diff --git a/CHANGELOG.md b/CHANGELOG.md
index e5d9138a..dec14540 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,16 +8,17 @@
### Updates
-- Added the emote feature to Workadventure. (@Kharhamel, @Tabascoeye)
+- Added the emote feature to WorkAdventure. (@Kharhamel, @Tabascoeye)
- The emote menu can be opened by clicking on your character.
- Clicking on one of its element will close the menu and play an emote above your character.
- This emote can be seen by other players.
- Mobile support has been improved
- - WorkAdventure automatically sets the zoom level based on the viewport size to ensure a sensible size of the map is visible, whatever the viewport used
- - Mouse wheel support to zoom in / out
- - Pinch support on mobile to zoom in / out
- - Improved virtual joystick size (adapts to the zoom level)
-
+ - WorkAdventure automatically sets the zoom level based on the viewport size to ensure a sensible size of the map is visible, whatever the viewport used
+ - Mouse wheel support to zoom in / out
+ - Pinch support on mobile to zoom in / out
+ - Improved virtual joystick size (adapts to the zoom level)
+- New scripting API features:
+ - Use `WA.loadSound(): Sound` to load / play / stop a sound
### Bug Fixes
diff --git a/back/yarn.lock b/back/yarn.lock
index 9469a69d..8af760c8 100644
--- a/back/yarn.lock
+++ b/back/yarn.lock
@@ -1251,9 +1251,9 @@ has-values@^1.0.0:
kind-of "^4.0.0"
hosted-git-info@^2.1.4:
- version "2.8.8"
- resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
- integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
+ version "2.8.9"
+ resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
+ integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
http-errors@1.7.2:
version "1.7.2"
diff --git a/benchmark/package-lock.json b/benchmark/package-lock.json
index 8d4db6cf..72d0aae4 100644
--- a/benchmark/package-lock.json
+++ b/benchmark/package-lock.json
@@ -230,9 +230,9 @@
}
},
"hosted-git-info": {
- "version": "2.8.8",
- "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
- "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg=="
+ "version": "2.8.9",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
+ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="
},
"indent-string": {
"version": "2.1.0",
diff --git a/benchmark/yarn.lock b/benchmark/yarn.lock
index d93e3667..f1209dcf 100644
--- a/benchmark/yarn.lock
+++ b/benchmark/yarn.lock
@@ -169,8 +169,8 @@ graceful-fs@^4.1.2:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
hosted-git-info@^2.1.4:
- version "2.8.8"
- resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
+ version "2.8.9"
+ resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
indent-string@^2.1.0:
version "2.1.0"
diff --git a/front/dist/index.tmpl.html b/front/dist/index.tmpl.html
index adbbfe44..7ef44116 100644
--- a/front/dist/index.tmpl.html
+++ b/front/dist/index.tmpl.html
@@ -73,7 +73,6 @@
-
@@ -108,7 +107,7 @@
-
+
reduce in conversations
diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts
index 1bab019a..bb15528d 100644
--- a/front/src/Api/Events/IframeEvent.ts
+++ b/front/src/Api/Events/IframeEvent.ts
@@ -15,6 +15,9 @@ import type { UserInputChatEvent } from './UserInputChatEvent';
import type { DataLayerEvent } from "./DataLayerEvent";
import type { LayerEvent } from './LayerEvent';
import type { SetPropertyEvent } from "./setPropertyEvent";
+import type {LoadSoundEvent} from "./LoadSoundEvent";
+import type {PlaySoundEvent} from "./PlaySoundEvent";
+
export interface TypedMessageEvent extends MessageEvent {
data: T
@@ -40,6 +43,9 @@ export type IframeEventMap = {
setProperty: SetPropertyEvent
getDataLayer: undefined
//tilsetEvent: TilesetEvent
+ loadSound: LoadSoundEvent
+ playSound: PlaySoundEvent
+ stopSound: null
}
export interface IframeEvent {
type: T;
diff --git a/front/src/Api/Events/LoadSoundEvent.ts b/front/src/Api/Events/LoadSoundEvent.ts
new file mode 100644
index 00000000..19b4b8e1
--- /dev/null
+++ b/front/src/Api/Events/LoadSoundEvent.ts
@@ -0,0 +1,11 @@
+import * as tg from "generic-type-guard";
+
+export const isLoadSoundEvent =
+ new tg.IsInterface().withProperties({
+ url: tg.isString,
+ }).get();
+
+/**
+ * A message sent from the iFrame to the game to add a message in the chat.
+ */
+export type LoadSoundEvent = tg.GuardedType;
diff --git a/front/src/Api/Events/PlaySoundEvent.ts b/front/src/Api/Events/PlaySoundEvent.ts
new file mode 100644
index 00000000..33ca1ff4
--- /dev/null
+++ b/front/src/Api/Events/PlaySoundEvent.ts
@@ -0,0 +1,24 @@
+import * as tg from "generic-type-guard";
+
+
+const isSoundConfig =
+ new tg.IsInterface().withProperties({
+ volume: tg.isOptional(tg.isNumber),
+ loop: tg.isOptional(tg.isBoolean),
+ mute: tg.isOptional(tg.isBoolean),
+ rate: tg.isOptional(tg.isNumber),
+ detune: tg.isOptional(tg.isNumber),
+ seek: tg.isOptional(tg.isNumber),
+ delay: tg.isOptional(tg.isNumber)
+ }).get();
+
+export const isPlaySoundEvent =
+ new tg.IsInterface().withProperties({
+ url: tg.isString,
+ config : tg.isOptional(isSoundConfig),
+ }).get();
+
+/**
+ * A message sent from the iFrame to the game to add a message in the chat.
+ */
+export type PlaySoundEvent = tg.GuardedType;
diff --git a/front/src/Api/Events/StopSoundEvent.ts b/front/src/Api/Events/StopSoundEvent.ts
new file mode 100644
index 00000000..6d12516d
--- /dev/null
+++ b/front/src/Api/Events/StopSoundEvent.ts
@@ -0,0 +1,11 @@
+import * as tg from "generic-type-guard";
+
+export const isStopSoundEvent =
+ new tg.IsInterface().withProperties({
+ url: tg.isString,
+ }).get();
+
+/**
+ * A message sent from the iFrame to the game to add a message in the chat.
+ */
+export type StopSoundEvent = tg.GuardedType;
diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts
index ec340b16..ceeea1c4 100644
--- a/front/src/Api/IframeListener.ts
+++ b/front/src/Api/IframeListener.ts
@@ -1,3 +1,4 @@
+
import { Subject } from "rxjs";
import { ChatEvent, isChatEvent } from "./Events/ChatEvent";
import { HtmlUtils } from "../WebRtc/HtmlUtils";
@@ -20,8 +21,9 @@ import type { DataLayerEvent } from "./Events/DataLayerEvent";
import { isMenuItemRegisterEvent } from './Events/MenuItemRegisterEvent';
import type { MenuItemClickedEvent } from './Events/MenuItemClickedEvent';
//import { isTilesetEvent, TilesetEvent } from "./Events/TilesetEvent";
-
-
+import { isPlaySoundEvent, PlaySoundEvent } from "./Events/PlaySoundEvent";
+import { isStopSoundEvent, StopSoundEvent } from "./Events/StopSoundEvent";
+import { isLoadSoundEvent, LoadSoundEvent } from "./Events/LoadSoundEvent";
/**
* Listens to messages from iframes and turn those messages into easy to use observables.
* Also allows to send messages to those iframes.
@@ -82,6 +84,15 @@ class IframeListener {
/* private readonly _tilesetLoaderStream: Subject = new Subject();
public readonly tilesetLoaderStream = this._tilesetLoaderStream.asObservable();*/
+ private readonly _playSoundStream: Subject = new Subject();
+ public readonly playSoundStream = this._playSoundStream.asObservable();
+
+ private readonly _stopSoundStream: Subject = new Subject();
+ public readonly stopSoundStream = this._stopSoundStream.asObservable();
+
+ private readonly _loadSoundStream: Subject = new Subject();
+ public readonly loadSoundStream = this._loadSoundStream.asObservable();
+
private readonly iframes = new Set();
private readonly scripts = new Map();
private sendPlayerMove: boolean = false;
@@ -123,6 +134,15 @@ class IframeListener {
else if (payload.type === 'goToPage' && isGoToPageEvent(payload.data)) {
scriptUtils.goToPage(payload.data.url);
}
+ else if (payload.type === 'playSound' && isPlaySoundEvent(payload.data)) {
+ this._playSoundStream.next(payload.data);
+ }
+ else if (payload.type === 'stopSound' && isStopSoundEvent(payload.data)) {
+ this._stopSoundStream.next(payload.data);
+ }
+ else if (payload.type === 'loadSound' && isLoadSoundEvent(payload.data)) {
+ this._loadSoundStream.next(payload.data);
+ }
else if (payload.type === 'openCoWebSite' && isOpenCoWebsite(payload.data)) {
const scriptUrl = [...this.scripts.keys()].find(key => {
return this.scripts.get(key)?.contentWindow == message.source
@@ -130,9 +150,11 @@ class IframeListener {
scriptUtils.openCoWebsite(payload.data.url, scriptUrl || foundSrc);
}
+
else if (payload.type === 'closeCoWebSite') {
scriptUtils.closeCoWebSite();
}
+
else if (payload.type === 'disablePlayerControls') {
this._disablePlayerControlStream.next();
}
diff --git a/front/src/Phaser/Game/DirtyScene.ts b/front/src/Phaser/Game/DirtyScene.ts
index 65831284..ecc81a99 100644
--- a/front/src/Phaser/Game/DirtyScene.ts
+++ b/front/src/Phaser/Game/DirtyScene.ts
@@ -12,6 +12,7 @@ export abstract class DirtyScene extends ResizableScene {
private isAlreadyTracking: boolean = false;
protected dirty:boolean = true;
private objectListChanged:boolean = true;
+ private physicsEnabled: boolean = false;
/**
* Track all objects added to the scene and adds a callback each time an animation is added.
@@ -38,6 +39,27 @@ export abstract class DirtyScene extends ResizableScene {
this.objectListChanged = false;
this.dirty = false;
});
+
+ this.physics.disableUpdate();
+ this.events.on(Events.POST_UPDATE, () => {
+ let objectMoving = false;
+ for (const body of this.physics.world.bodies.entries) {
+ if (body.velocity.x !== 0 || body.velocity.y !== 0) {
+ this.objectListChanged = true;
+ objectMoving = true;
+ if (!this.physicsEnabled) {
+ this.physics.enableUpdate();
+ this.physicsEnabled = true;
+ }
+ break;
+ }
+ }
+ if (!objectMoving && this.physicsEnabled) {
+ this.physics.disableUpdate();
+ this.physicsEnabled = false;
+ }
+ });
+
}
private trackAnimation(): void {
diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts
index 338625b1..1e4c55f5 100644
--- a/front/src/Phaser/Game/GameScene.ts
+++ b/front/src/Phaser/Game/GameScene.ts
@@ -52,6 +52,7 @@ import { mediaManager } from "../../WebRtc/MediaManager";
import type { ItemFactoryInterface } from "../Items/ItemFactoryInterface";
import type { ActionableItem } from "../Items/ActionableItem";
import { UserInputManager } from "../UserInput/UserInputManager";
+import {soundManager} from "./SoundManager";
import type { UserMovedMessage } from "../../Messages/generated/messages_pb";
import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils";
import { connectionManager } from "../../Connexion/ConnectionManager";
@@ -92,7 +93,8 @@ import { PinchManager } from "../UserInput/PinchManager";
import { joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey } from "../Components/MobileJoystick";
import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes";
import { waScaleManager } from "../Services/WaScaleManager";
-import { EmoteManager } from "./EmoteManager";
+import { peerStore} from "../../Stores/PeerStore";
+import {EmoteManager } from "./EmoteManager";
import type { HasPlayerMovedEvent } from '../../Api/Events/HasPlayerMovedEvent';
import { MenuScene, MenuSceneName } from '../Menu/MenuScene';
@@ -189,9 +191,7 @@ export class GameScene extends DirtyScene implements CenterListener {
private popUpElements : Map = new Map();
private originalMapUrl: string | undefined;
private pinchManager: PinchManager | undefined;
- private physicsEnabled: boolean = true;
private mapTransitioning: boolean = false; //used to prevent transitions happenning at the same time.
- private onVisibilityChangeCallback: () => void;
private emoteManager!: EmoteManager;
constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) {
@@ -212,7 +212,6 @@ export class GameScene extends DirtyScene implements CenterListener {
this.connectionAnswerPromise = new Promise((resolve, reject): void => {
this.connectionAnswerPromiseResolve = resolve;
});
- this.onVisibilityChangeCallback = this.onVisibilityChange.bind(this);
}
//hook preload scene
@@ -507,8 +506,6 @@ export class GameScene extends DirtyScene implements CenterListener {
if (!this.room.isDisconnected()) {
this.connect();
}
- console.log('display');
- document.addEventListener('visibilitychange', this.onVisibilityChangeCallback);
this.emoteManager = new EmoteManager(this);
}
@@ -615,6 +612,7 @@ export class GameScene extends DirtyScene implements CenterListener {
// When connection is performed, let's connect SimplePeer
this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic, this.playerName);
+ peerStore.connectToSimplePeer(this.simplePeer);
this.GlobalMessageManager = new GlobalMessageManager(this.connection);
userMessageManager.setReceiveBanListener(this.bannedUser.bind(this));
@@ -632,7 +630,6 @@ export class GameScene extends DirtyScene implements CenterListener {
self.chatModeSprite.setVisible(false);
self.openChatIcon.setVisible(false);
audioManager.restoreVolume();
- self.onVisibilityChange();
}
}
})
@@ -870,6 +867,24 @@ ${escapedMessage}
this.userInputManager.disableControls();
}));
+ this.iframeSubscriptionList.push(iframeListener.playSoundStream.subscribe((playSoundEvent)=>
+ {
+ const url = new URL(playSoundEvent.url, this.MapUrlFile);
+ soundManager.playSound(this.load,this.sound,url.toString(),playSoundEvent.config);
+ }))
+
+ this.iframeSubscriptionList.push(iframeListener.stopSoundStream.subscribe((stopSoundEvent)=>
+ {
+ const url = new URL(stopSoundEvent.url, this.MapUrlFile);
+ soundManager.stopSound(this.sound,url.toString());
+ }))
+
+ this.iframeSubscriptionList.push(iframeListener.loadSoundStream.subscribe((loadSoundEvent)=>
+ {
+ const url = new URL(loadSoundEvent.url, this.MapUrlFile);
+ soundManager.loadSound(this.load,this.sound,url.toString());
+ }))
+
this.iframeSubscriptionList.push(iframeListener.enablePlayerControlStream.subscribe(() => {
this.userInputManager.restoreControls();
}));
@@ -1000,8 +1015,6 @@ ${escapedMessage}
for (const iframeEvents of this.iframeSubscriptionList) {
iframeEvents.unsubscribe();
}
-
- document.removeEventListener('visibilitychange', this.onVisibilityChangeCallback);
}
private removeAllRemotePlayers(): void {
@@ -1150,8 +1163,6 @@ ${escapedMessage}
}
createCollisionWithPlayer() {
- this.physics.disableUpdate();
- this.physicsEnabled = false;
//add collision layer
for (const phaserLayer of this.gameMap.phaserLayers) {
if (phaserLayer.type == "tilelayer") {
@@ -1294,20 +1305,7 @@ ${escapedMessage}
update(time: number, delta: number): void {
mediaManager.updateScene();
this.currentTick = time;
- if (this.CurrentPlayer.isMoving()) {
- this.dirty = true;
- }
this.CurrentPlayer.moveUser(delta);
- if (this.CurrentPlayer.isMoving()) {
- this.dirty = true;
- if (!this.physicsEnabled) {
- this.physics.enableUpdate();
- this.physicsEnabled = true;
- }
- } else if (this.physicsEnabled) {
- this.physics.disableUpdate();
- this.physicsEnabled = false;
- }
// Let's handle all events
while (this.pendingEvents.length !== 0) {
@@ -1575,8 +1573,6 @@ ${escapedMessage}
mediaManager.addTriggerCloseJitsiFrameButton('close-jisi', () => {
this.stopJitsi();
});
-
- this.onVisibilityChange();
}
public stopJitsi(): void {
@@ -1585,7 +1581,6 @@ ${escapedMessage}
mediaManager.showGameOverlay();
mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi');
- this.onVisibilityChange();
}
//todo: put this into an 'orchestrator' scene (EntryScene?)
@@ -1625,20 +1620,4 @@ ${escapedMessage}
waScaleManager.zoomModifier *= zoomFactor;
this.updateCameraOffset();
}
-
- private onVisibilityChange(): void {
- // If the overlay is not displayed, we are in Jitsi. We don't need the webcam.
- if (!mediaManager.isGameOverlayVisible()) {
- mediaManager.blurCamera();
- return;
- }
-
- if (document.visibilityState === 'visible') {
- mediaManager.focusCamera();
- } else {
- if (this.simplePeer.getNbConnections() === 0) {
- mediaManager.blurCamera();
- }
- }
- }
}
diff --git a/front/src/Phaser/Game/SoundManager.ts b/front/src/Phaser/Game/SoundManager.ts
new file mode 100644
index 00000000..f0210494
--- /dev/null
+++ b/front/src/Phaser/Game/SoundManager.ts
@@ -0,0 +1,37 @@
+import LoaderPlugin = Phaser.Loader.LoaderPlugin;
+import BaseSoundManager = Phaser.Sound.BaseSoundManager;
+import BaseSound = Phaser.Sound.BaseSound;
+import SoundConfig = Phaser.Types.Sound.SoundConfig;
+
+class SoundManager {
+ private soundPromises : Map> = new Map>();
+ public loadSound (loadPlugin: LoaderPlugin, soundManager : BaseSoundManager, soundUrl: string) : Promise {
+ let soundPromise = this.soundPromises.get(soundUrl);
+ if (soundPromise !== undefined) {
+ return soundPromise;
+ }
+ soundPromise = new Promise((res) => {
+
+ const sound = soundManager.get(soundUrl);
+ if (sound !== null) {
+ return res(sound);
+ }
+ loadPlugin.audio(soundUrl, soundUrl);
+ loadPlugin.once('filecomplete-audio-' + soundUrl, () => res(soundManager.add(soundUrl)));
+ loadPlugin.start();
+ });
+ this.soundPromises.set(soundUrl,soundPromise);
+ return soundPromise;
+ }
+
+ public async playSound(loadPlugin: LoaderPlugin, soundManager : BaseSoundManager, soundUrl: string, config: SoundConfig|undefined) : Promise {
+ const sound = await this.loadSound(loadPlugin,soundManager,soundUrl);
+ if (config === undefined) sound.play();
+ else sound.play(config);
+ }
+
+ public stopSound(soundManager : BaseSoundManager,soundUrl : string){
+ soundManager.get(soundUrl).stop();
+ }
+}
+export const soundManager = new SoundManager();
diff --git a/front/src/Phaser/Login/EnableCameraScene.ts b/front/src/Phaser/Login/EnableCameraScene.ts
index 755ac9a0..6002da7b 100644
--- a/front/src/Phaser/Login/EnableCameraScene.ts
+++ b/front/src/Phaser/Login/EnableCameraScene.ts
@@ -10,6 +10,14 @@ import {PinchManager} from "../UserInput/PinchManager";
import Zone = Phaser.GameObjects.Zone;
import { MenuScene } from "../Menu/MenuScene";
import {ResizableScene} from "./ResizableScene";
+import {
+ audioConstraintStore,
+ enableCameraSceneVisibilityStore,
+ localStreamStore,
+ mediaStreamConstraintsStore,
+ videoConstraintStore
+} from "../../Stores/MediaStore";
+import type {Unsubscriber} from "svelte/store";
export const EnableCameraSceneName = "EnableCameraScene";
enum LoginTextures {
@@ -40,6 +48,7 @@ export class EnableCameraScene extends ResizableScene {
private enableCameraSceneElement!: Phaser.GameObjects.DOMElement;
private mobileTapZone!: Zone;
+ private localStreamStoreUnsubscriber!: Unsubscriber;
constructor() {
super({
@@ -119,9 +128,20 @@ export class EnableCameraScene extends ResizableScene {
HtmlUtils.getElementByIdOrFail('webRtcSetup').classList.add('active');
- const mediaPromise = mediaManager.getCamera();
+ this.localStreamStoreUnsubscriber = localStreamStore.subscribe((result) => {
+ if (result.type === 'error') {
+ // TODO: proper handling of the error
+ throw result.error;
+ }
+
+ this.getDevices();
+ if (result.stream !== null) {
+ this.setupStream(result.stream);
+ }
+ });
+ /*const mediaPromise = mediaManager.getCamera();
mediaPromise.then(this.getDevices.bind(this));
- mediaPromise.then(this.setupStream.bind(this));
+ mediaPromise.then(this.setupStream.bind(this));*/
this.input.keyboard.on('keydown-RIGHT', this.nextCam.bind(this));
this.input.keyboard.on('keydown-LEFT', this.previousCam.bind(this));
@@ -133,6 +153,8 @@ export class EnableCameraScene extends ResizableScene {
this.add.existing(this.soundMeterSprite);
this.onResize();
+
+ enableCameraSceneVisibilityStore.showEnableCameraScene();
}
private previousCam(): void {
@@ -140,7 +162,9 @@ export class EnableCameraScene extends ResizableScene {
return;
}
this.cameraSelected--;
- mediaManager.setCamera(this.camerasList[this.cameraSelected].deviceId).then(this.setupStream.bind(this));
+ videoConstraintStore.setDeviceId(this.camerasList[this.cameraSelected].deviceId);
+
+ //mediaManager.setCamera(this.camerasList[this.cameraSelected].deviceId).then(this.setupStream.bind(this));
}
private nextCam(): void {
@@ -148,8 +172,10 @@ export class EnableCameraScene extends ResizableScene {
return;
}
this.cameraSelected++;
+ videoConstraintStore.setDeviceId(this.camerasList[this.cameraSelected].deviceId);
+
// TODO: the change of camera should be OBSERVED (reactive)
- mediaManager.setCamera(this.camerasList[this.cameraSelected].deviceId).then(this.setupStream.bind(this));
+ //mediaManager.setCamera(this.camerasList[this.cameraSelected].deviceId).then(this.setupStream.bind(this));
}
private previousMic(): void {
@@ -157,7 +183,8 @@ export class EnableCameraScene extends ResizableScene {
return;
}
this.microphoneSelected--;
- mediaManager.setMicrophone(this.microphonesList[this.microphoneSelected].deviceId).then(this.setupStream.bind(this));
+ audioConstraintStore.setDeviceId(this.microphonesList[this.microphoneSelected].deviceId);
+ //mediaManager.setMicrophone(this.microphonesList[this.microphoneSelected].deviceId).then(this.setupStream.bind(this));
}
private nextMic(): void {
@@ -165,8 +192,9 @@ export class EnableCameraScene extends ResizableScene {
return;
}
this.microphoneSelected++;
+ audioConstraintStore.setDeviceId(this.microphonesList[this.microphoneSelected].deviceId);
// TODO: the change of camera should be OBSERVED (reactive)
- mediaManager.setMicrophone(this.microphonesList[this.microphoneSelected].deviceId).then(this.setupStream.bind(this));
+ //mediaManager.setMicrophone(this.microphonesList[this.microphoneSelected].deviceId).then(this.setupStream.bind(this));
}
/**
@@ -260,15 +288,20 @@ export class EnableCameraScene extends ResizableScene {
HtmlUtils.getElementByIdOrFail('webRtcSetup').style.display = 'none';
this.soundMeter.stop();
- mediaManager.stopCamera();
- mediaManager.stopMicrophone();
+ enableCameraSceneVisibilityStore.hideEnableCameraScene();
+ this.localStreamStoreUnsubscriber();
+ //mediaManager.stopCamera();
+ //mediaManager.stopMicrophone();
- this.scene.sleep(EnableCameraSceneName)
+ this.scene.sleep(EnableCameraSceneName);
gameManager.goToStartingMap(this.scene);
}
private async getDevices() {
+ // TODO: switch this in a store.
const mediaDeviceInfos = await navigator.mediaDevices.enumerateDevices();
+ this.microphonesList = [];
+ this.camerasList = [];
for (const mediaDeviceInfo of mediaDeviceInfos) {
if (mediaDeviceInfo.kind === 'audioinput') {
this.microphonesList.push(mediaDeviceInfo);
diff --git a/front/src/Phaser/Menu/HelpCameraSettingsScene.ts b/front/src/Phaser/Menu/HelpCameraSettingsScene.ts
index 6e80b8d4..6bc520c0 100644
--- a/front/src/Phaser/Menu/HelpCameraSettingsScene.ts
+++ b/front/src/Phaser/Menu/HelpCameraSettingsScene.ts
@@ -2,6 +2,8 @@ import {mediaManager} from "../../WebRtc/MediaManager";
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {DirtyScene} from "../Game/DirtyScene";
+import {get} from "svelte/store";
+import {requestedCameraState, requestedMicrophoneState} from "../../Stores/MediaStore";
export const HelpCameraSettingsSceneName = 'HelpCameraSettingsScene';
const helpCameraSettings = 'helpCameraSettings';
@@ -41,7 +43,7 @@ export class HelpCameraSettingsScene extends DirtyScene {
}
});
- if(!localUserStore.getHelpCameraSettingsShown() && (!mediaManager.constraintsMedia.audio || !mediaManager.constraintsMedia.video)){
+ if(!localUserStore.getHelpCameraSettingsShown() && (!get(requestedMicrophoneState) || !get(requestedCameraState))){
this.openHelpCameraSettingsOpened();
localUserStore.setHelpCameraSettingsShown();
}
diff --git a/front/src/Phaser/Menu/MenuScene.ts b/front/src/Phaser/Menu/MenuScene.ts
index c6c59476..8957bbce 100644
--- a/front/src/Phaser/Menu/MenuScene.ts
+++ b/front/src/Phaser/Menu/MenuScene.ts
@@ -13,6 +13,7 @@ import {menuIconVisible} from "../../Stores/MenuStore";
import { HtmlUtils } from '../../WebRtc/HtmlUtils';
import { iframeListener } from '../../Api/IframeListener';
import { Subscription } from 'rxjs';
+import { videoConstraintStore } from "../../Stores/MediaStore";
export const MenuSceneName = 'MenuScene';
const gameMenuKey = 'gameMenu';
@@ -356,7 +357,7 @@ export class MenuScene extends Phaser.Scene {
if (valueVideo !== this.videoQualityValue) {
this.videoQualityValue = valueVideo;
localUserStore.setVideoQualityValue(valueVideo);
- mediaManager.updateCameraQuality(valueVideo);
+ videoConstraintStore.setFrameRate(valueVideo);
}
this.closeGameQualityMenu();
}
diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts
index b971407b..b7f31aad 100644
--- a/front/src/Phaser/Player/Player.ts
+++ b/front/src/Phaser/Player/Player.ts
@@ -2,6 +2,7 @@ import {PlayerAnimationDirections} from "./Animation";
import type {GameScene} from "../Game/GameScene";
import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager";
import {Character} from "../Entity/Character";
+import {userMovingStore} from "../../Stores/GameStore";
import {RadialMenu, RadialMenuClickEvent, RadialMenuItem} from "../Components/RadialMenu";
export const hasMovedEventName = "hasMoved";
@@ -86,6 +87,7 @@ export class Player extends Character {
this.previousDirection = direction;
}
this.wasMoving = moving;
+ userMovingStore.set(moving);
}
public isMoving(): boolean {
@@ -99,7 +101,7 @@ export class Player extends Character {
this.openEmoteMenu(emotes);
}
}
-
+
isClickable(): boolean {
return true;
}
@@ -113,13 +115,13 @@ export class Player extends Character {
this.playEmote(item.name);
});
}
-
+
closeEmoteMenu(): void {
if (!this.emoteMenu) return;
this.emoteMenu.destroy();
this.emoteMenu = null;
}
-
+
destroy() {
this.scene.events.removeListener('postupdate', this.updateListener);
super.destroy();
diff --git a/front/src/Stores/GameStore.ts b/front/src/Stores/GameStore.ts
new file mode 100644
index 00000000..ee975f23
--- /dev/null
+++ b/front/src/Stores/GameStore.ts
@@ -0,0 +1,3 @@
+import { derived, writable, Writable } from "svelte/store";
+
+export const userMovingStore = writable(false);
diff --git a/front/src/Stores/MediaStore.ts b/front/src/Stores/MediaStore.ts
new file mode 100644
index 00000000..9e53aa3b
--- /dev/null
+++ b/front/src/Stores/MediaStore.ts
@@ -0,0 +1,510 @@
+import {derived, get, Readable, readable, writable, Writable} from "svelte/store";
+import {peerStore} from "./PeerStore";
+import {localUserStore} from "../Connexion/LocalUserStore";
+import {ITiledMapGroupLayer, ITiledMapObjectLayer, ITiledMapTileLayer} from "../Phaser/Map/ITiledMap";
+import {userMovingStore} from "./GameStore";
+import {HtmlUtils} from "../WebRtc/HtmlUtils";
+
+/**
+ * A store that contains the camera state requested by the user (on or off).
+ */
+function createRequestedCameraState() {
+ const { subscribe, set, update } = writable(true);
+
+ return {
+ subscribe,
+ enableWebcam: () => set(true),
+ disableWebcam: () => set(false),
+ };
+}
+
+/**
+ * A store that contains the microphone state requested by the user (on or off).
+ */
+function createRequestedMicrophoneState() {
+ const { subscribe, set, update } = writable(true);
+
+ return {
+ subscribe,
+ enableMicrophone: () => set(true),
+ disableMicrophone: () => set(false),
+ };
+}
+
+/**
+ * A store containing whether the current page is visible or not.
+ */
+export const visibilityStore = readable(document.visibilityState === 'visible', function start(set) {
+ const onVisibilityChange = () => {
+ set(document.visibilityState === 'visible');
+ };
+
+ document.addEventListener('visibilitychange', onVisibilityChange);
+
+ return function stop() {
+ document.removeEventListener('visibilitychange', onVisibilityChange);
+ };
+});
+
+/**
+ * A store that contains whether the game overlay is shown or not.
+ * Typically, the overlay is hidden when entering Jitsi meet.
+ */
+function createGameOverlayVisibilityStore() {
+ const { subscribe, set, update } = writable(false);
+
+ return {
+ subscribe,
+ showGameOverlay: () => set(true),
+ hideGameOverlay: () => set(false),
+ };
+}
+
+/**
+ * A store that contains whether the EnableCameraScene is shown or not.
+ */
+function createEnableCameraSceneVisibilityStore() {
+ const { subscribe, set, update } = writable(false);
+
+ return {
+ subscribe,
+ showEnableCameraScene: () => set(true),
+ hideEnableCameraScene: () => set(false),
+ };
+}
+
+export const requestedCameraState = createRequestedCameraState();
+export const requestedMicrophoneState = createRequestedMicrophoneState();
+export const gameOverlayVisibilityStore = createGameOverlayVisibilityStore();
+export const enableCameraSceneVisibilityStore = createEnableCameraSceneVisibilityStore();
+
+/**
+ * A store that contains "true" if the webcam should be stopped for privacy reasons - i.e. if the the user left the the page while not in a discussion.
+ */
+function createPrivacyShutdownStore() {
+ let privacyEnabled = false;
+
+ const { subscribe, set, update } = writable(privacyEnabled);
+
+ visibilityStore.subscribe((isVisible) => {
+ if (!isVisible && get(peerStore).size === 0) {
+ privacyEnabled = true;
+ set(true);
+ }
+ if (isVisible) {
+ privacyEnabled = false;
+ set(false);
+ }
+ });
+
+ peerStore.subscribe((peers) => {
+ if (peers.size === 0 && get(visibilityStore) === false) {
+ privacyEnabled = true;
+ set(true);
+ }
+ });
+
+
+ return {
+ subscribe,
+ };
+}
+
+export const privacyShutdownStore = createPrivacyShutdownStore();
+
+
+/**
+ * A store containing whether the webcam was enabled in the last 10 seconds
+ */
+const enabledWebCam10secondsAgoStore = readable(false, function start(set) {
+ let timeout: NodeJS.Timeout|null = null;
+
+ const unsubscribe = requestedCameraState.subscribe((enabled) => {
+ if (enabled === true) {
+ if (timeout) {
+ clearTimeout(timeout);
+ }
+ timeout = setTimeout(() => {
+ set(false);
+ }, 10000);
+ set(true);
+ } else {
+ set(false);
+ }
+ })
+
+ return function stop() {
+ unsubscribe();
+ };
+});
+
+/**
+ * A store containing whether the webcam was enabled in the last 5 seconds
+ */
+const userMoved5SecondsAgoStore = readable(false, function start(set) {
+ let timeout: NodeJS.Timeout|null = null;
+
+ const unsubscribe = userMovingStore.subscribe((moving) => {
+ if (moving === true) {
+ if (timeout) {
+ clearTimeout(timeout);
+ }
+ set(true);
+ } else {
+ timeout = setTimeout(() => {
+ set(false);
+ }, 5000);
+
+ }
+ })
+
+ return function stop() {
+ unsubscribe();
+ };
+});
+
+
+/**
+ * A store containing whether the mouse is getting close the bottom right corner.
+ */
+const mouseInBottomRight = readable(false, function start(set) {
+ let lastInBottomRight = false;
+ const gameDiv = HtmlUtils.getElementByIdOrFail('game');
+
+ const detectInBottomRight = (event: MouseEvent) => {
+ const rect = gameDiv.getBoundingClientRect();
+ const inBottomRight = event.x - rect.left > rect.width * 3 / 4 && event.y - rect.top > rect.height * 3 / 4;
+ if (inBottomRight !== lastInBottomRight) {
+ lastInBottomRight = inBottomRight;
+ set(inBottomRight);
+ }
+ };
+
+ document.addEventListener('mousemove', detectInBottomRight);
+
+ return function stop() {
+ document.removeEventListener('mousemove', detectInBottomRight);
+ }
+});
+
+/**
+ * A store that contains "true" if the webcam should be stopped for energy efficiency reason - i.e. we are not moving and not in a conversation.
+ */
+export const cameraEnergySavingStore = derived([userMoved5SecondsAgoStore, peerStore, enabledWebCam10secondsAgoStore, mouseInBottomRight], ([$userMoved5SecondsAgoStore,$peerStore, $enabledWebCam10secondsAgoStore, $mouseInBottomRight]) => {
+ return !$mouseInBottomRight && !$userMoved5SecondsAgoStore && $peerStore.size === 0 && !$enabledWebCam10secondsAgoStore;
+});
+
+/**
+ * A store that contains video constraints.
+ */
+function createVideoConstraintStore() {
+ const { subscribe, set, update } = writable({
+ width: { min: 640, ideal: 1280, max: 1920 },
+ height: { min: 400, ideal: 720 },
+ frameRate: { ideal: localUserStore.getVideoQualityValue() },
+ facingMode: "user",
+ resizeMode: 'crop-and-scale',
+ aspectRatio: 1.777777778
+ } as MediaTrackConstraints);
+
+ return {
+ subscribe,
+ setDeviceId: (deviceId: string) => update((constraints) => {
+ constraints.deviceId = {
+ exact: deviceId
+ };
+
+ return constraints;
+ }),
+ setFrameRate: (frameRate: number) => update((constraints) => {
+ constraints.frameRate = { ideal: frameRate };
+
+ return constraints;
+ })
+ };
+}
+
+export const videoConstraintStore = createVideoConstraintStore();
+
+/**
+ * A store that contains video constraints.
+ */
+function createAudioConstraintStore() {
+ const { subscribe, set, update } = writable({
+ //TODO: make these values configurable in the game settings menu and store them in localstorage
+ autoGainControl: false,
+ echoCancellation: true,
+ noiseSuppression: true
+ } as boolean|MediaTrackConstraints);
+
+ let selectedDeviceId = null;
+
+ return {
+ subscribe,
+ setDeviceId: (deviceId: string) => update((constraints) => {
+ selectedDeviceId = deviceId;
+
+ if (typeof(constraints) === 'boolean') {
+ constraints = {}
+ }
+ constraints.deviceId = {
+ exact: selectedDeviceId
+ };
+
+ return constraints;
+ })
+ };
+}
+
+export const audioConstraintStore = createAudioConstraintStore();
+
+
+let timeout: NodeJS.Timeout;
+
+let previousComputedVideoConstraint: boolean|MediaTrackConstraints = false;
+let previousComputedAudioConstraint: boolean|MediaTrackConstraints = false;
+
+/**
+ * A store containing the media constraints we want to apply.
+ */
+export const mediaStreamConstraintsStore = derived(
+ [
+ requestedCameraState,
+ requestedMicrophoneState,
+ gameOverlayVisibilityStore,
+ enableCameraSceneVisibilityStore,
+ videoConstraintStore,
+ audioConstraintStore,
+ privacyShutdownStore,
+ cameraEnergySavingStore,
+ ], (
+ [
+ $requestedCameraState,
+ $requestedMicrophoneState,
+ $gameOverlayVisibilityStore,
+ $enableCameraSceneVisibilityStore,
+ $videoConstraintStore,
+ $audioConstraintStore,
+ $privacyShutdownStore,
+ $cameraEnergySavingStore,
+ ], set
+ ) => {
+
+ let currentVideoConstraint: boolean|MediaTrackConstraints = $videoConstraintStore;
+ let currentAudioConstraint: boolean|MediaTrackConstraints = $audioConstraintStore;
+
+ if ($enableCameraSceneVisibilityStore) {
+ set({
+ video: currentVideoConstraint,
+ audio: currentAudioConstraint,
+ });
+ return;
+ }
+
+ // Disable webcam if the user requested so
+ if ($requestedCameraState === false) {
+ currentVideoConstraint = false;
+ }
+
+ // Disable microphone if the user requested so
+ if ($requestedMicrophoneState === false) {
+ currentAudioConstraint = false;
+ }
+
+ // Disable webcam and microphone when in a Jitsi
+ if ($gameOverlayVisibilityStore === false) {
+ currentVideoConstraint = false;
+ currentAudioConstraint = false;
+ }
+
+ // Disable webcam for privacy reasons (the game is not visible and we were talking to noone)
+ if ($privacyShutdownStore === true) {
+ currentVideoConstraint = false;
+ }
+
+ // Disable webcam for energy reasons (the user is not moving and we are talking to noone)
+ if ($cameraEnergySavingStore === true) {
+ currentVideoConstraint = false;
+ currentAudioConstraint = false;
+ }
+
+ // Let's make the changes only if the new value is different from the old one.
+ if (previousComputedVideoConstraint != currentVideoConstraint || previousComputedAudioConstraint != currentAudioConstraint) {
+ previousComputedVideoConstraint = currentVideoConstraint;
+ previousComputedAudioConstraint = currentAudioConstraint;
+ // Let's copy the objects.
+ if (typeof previousComputedVideoConstraint !== 'boolean') {
+ previousComputedVideoConstraint = {...previousComputedVideoConstraint};
+ }
+ if (typeof previousComputedAudioConstraint !== 'boolean') {
+ previousComputedAudioConstraint = {...previousComputedAudioConstraint};
+ }
+
+ if (timeout) {
+ clearTimeout(timeout);
+ }
+
+ // Let's wait a little bit to avoid sending too many constraint changes.
+ timeout = setTimeout(() => {
+ set({
+ video: currentVideoConstraint,
+ audio: currentAudioConstraint,
+ });
+ }, 100);
+ }
+}, {
+ video: false,
+ audio: false
+} as MediaStreamConstraints);
+
+export type LocalStreamStoreValue = StreamSuccessValue | StreamErrorValue;
+
+interface StreamSuccessValue {
+ type: "success",
+ stream: MediaStream|null,
+ // The constraints that we got (and not the one that have been requested)
+ constraints: MediaStreamConstraints
+}
+
+interface StreamErrorValue {
+ type: "error",
+ error: Error,
+ constraints: MediaStreamConstraints
+}
+
+let currentStream : MediaStream|null = null;
+
+/**
+ * Stops the camera from filming
+ */
+function stopCamera(): void {
+ if (currentStream) {
+ for (const track of currentStream.getVideoTracks()) {
+ track.stop();
+ }
+ }
+}
+
+/**
+ * Stops the microphone from listening
+ */
+function stopMicrophone(): void {
+ if (currentStream) {
+ for (const track of currentStream.getAudioTracks()) {
+ track.stop();
+ }
+ }
+}
+
+/**
+ * A store containing the MediaStream object (or null if nothing requested, or Error if an error occurred)
+ */
+export const localStreamStore = derived, LocalStreamStoreValue>(mediaStreamConstraintsStore, ($mediaStreamConstraintsStore, set) => {
+ const constraints = { ...$mediaStreamConstraintsStore };
+
+ if (navigator.mediaDevices === undefined) {
+ if (window.location.protocol === 'http:') {
+ //throw new Error('Unable to access your camera or microphone. You need to use a HTTPS connection.');
+ set({
+ type: 'error',
+ error: new Error('Unable to access your camera or microphone. You need to use a HTTPS connection.'),
+ constraints
+ });
+ } else {
+ //throw new Error('Unable to access your camera or microphone. Your browser is too old.');
+ set({
+ type: 'error',
+ error: new Error('Unable to access your camera or microphone. Your browser is too old.'),
+ constraints
+ });
+ }
+ }
+
+ if (constraints.audio === false) {
+ stopMicrophone();
+ }
+ if (constraints.video === false) {
+ stopCamera();
+ }
+
+ if (constraints.audio === false && constraints.video === false) {
+ currentStream = null;
+ set({
+ type: 'success',
+ stream: null,
+ constraints
+ });
+ return;
+ }
+
+ (async () => {
+ try {
+ stopMicrophone();
+ stopCamera();
+ currentStream = await navigator.mediaDevices.getUserMedia(constraints);
+ set({
+ type: 'success',
+ stream: currentStream,
+ constraints
+ });
+ return;
+ } catch (e) {
+ if (constraints.video !== false) {
+ console.info("Error. Unable to get microphone and/or camera access. Trying audio only.", $mediaStreamConstraintsStore, e);
+ // TODO: does it make sense to pop this error when retrying?
+ set({
+ type: 'error',
+ error: e,
+ constraints
+ });
+ // Let's try without video constraints
+ requestedCameraState.disableWebcam();
+ } else {
+ console.info("Error. Unable to get microphone and/or camera access.", $mediaStreamConstraintsStore, e);
+ set({
+ type: 'error',
+ error: e,
+ constraints
+ });
+ }
+
+ /*constraints.video = false;
+ if (constraints.audio === false) {
+ console.info("Error. Unable to get microphone and/or camera access.", $mediaStreamConstraintsStore, e);
+ set({
+ type: 'error',
+ error: e,
+ constraints
+ });
+ // Let's make as if the user did not ask.
+ requestedCameraState.disableWebcam();
+ } else {
+ console.info("Error. Unable to get microphone and/or camera access. Trying audio only.", $mediaStreamConstraintsStore, e);
+ try {
+ currentStream = await navigator.mediaDevices.getUserMedia(constraints);
+ set({
+ type: 'success',
+ stream: currentStream,
+ constraints
+ });
+ return;
+ } catch (e2) {
+ console.info("Error. Unable to get microphone fallback access.", $mediaStreamConstraintsStore, e2);
+ set({
+ type: 'error',
+ error: e,
+ constraints
+ });
+ }
+ }*/
+ }
+ })();
+});
+
+/**
+ * A store containing the real active media constrained (not the one requested by the user, but the one we got from the system)
+ */
+export const obtainedMediaConstraintStore = derived(localStreamStore, ($localStreamStore) => {
+ return $localStreamStore.constraints;
+});
+
diff --git a/front/src/Stores/PeerStore.ts b/front/src/Stores/PeerStore.ts
new file mode 100644
index 00000000..a582e692
--- /dev/null
+++ b/front/src/Stores/PeerStore.ts
@@ -0,0 +1,36 @@
+import { derived, writable, Writable } from "svelte/store";
+import type {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
+import type {SimplePeer} from "../WebRtc/SimplePeer";
+
+/**
+ * A store that contains the camera state requested by the user (on or off).
+ */
+function createPeerStore() {
+ let users = new Map();
+
+ const { subscribe, set, update } = writable(users);
+
+ return {
+ subscribe,
+ connectToSimplePeer: (simplePeer: SimplePeer) => {
+ users = new Map();
+ set(users);
+ simplePeer.registerPeerConnectionListener({
+ onConnect(user: UserSimplePeerInterface) {
+ update(users => {
+ users.set(user.userId, user);
+ return users;
+ });
+ },
+ onDisconnect(userId: number) {
+ update(users => {
+ users.delete(userId);
+ return users;
+ });
+ }
+ })
+ }
+ };
+}
+
+export const peerStore = createPeerStore();
diff --git a/front/src/Stores/ScreenSharingStore.ts b/front/src/Stores/ScreenSharingStore.ts
new file mode 100644
index 00000000..a55f5ab2
--- /dev/null
+++ b/front/src/Stores/ScreenSharingStore.ts
@@ -0,0 +1,192 @@
+import {derived, get, Readable, readable, writable, Writable} from "svelte/store";
+import {peerStore} from "./PeerStore";
+import {localUserStore} from "../Connexion/LocalUserStore";
+import {ITiledMapGroupLayer, ITiledMapObjectLayer, ITiledMapTileLayer} from "../Phaser/Map/ITiledMap";
+import {userMovingStore} from "./GameStore";
+import {HtmlUtils} from "../WebRtc/HtmlUtils";
+import {
+ audioConstraintStore, cameraEnergySavingStore,
+ enableCameraSceneVisibilityStore,
+ gameOverlayVisibilityStore, LocalStreamStoreValue, privacyShutdownStore,
+ requestedCameraState,
+ requestedMicrophoneState, videoConstraintStore
+} from "./MediaStore";
+
+declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
+
+/**
+ * A store that contains the camera state requested by the user (on or off).
+ */
+function createRequestedScreenSharingState() {
+ const { subscribe, set, update } = writable(false);
+
+ return {
+ subscribe,
+ enableScreenSharing: () => set(true),
+ disableScreenSharing: () => set(false),
+ };
+}
+
+export const requestedScreenSharingState = createRequestedScreenSharingState();
+
+let currentStream : MediaStream|null = null;
+
+/**
+ * Stops the camera from filming
+ */
+function stopScreenSharing(): void {
+ if (currentStream) {
+ for (const track of currentStream.getVideoTracks()) {
+ track.stop();
+ }
+ }
+ currentStream = null;
+}
+
+let previousComputedVideoConstraint: boolean|MediaTrackConstraints = false;
+let previousComputedAudioConstraint: boolean|MediaTrackConstraints = false;
+
+/**
+ * A store containing the media constraints we want to apply.
+ */
+export const screenSharingConstraintsStore = derived(
+ [
+ requestedScreenSharingState,
+ gameOverlayVisibilityStore,
+ peerStore,
+ ], (
+ [
+ $requestedScreenSharingState,
+ $gameOverlayVisibilityStore,
+ $peerStore,
+ ], set
+ ) => {
+
+ let currentVideoConstraint: boolean|MediaTrackConstraints = true;
+ let currentAudioConstraint: boolean|MediaTrackConstraints = false;
+
+ // Disable screen sharing if the user requested so
+ if (!$requestedScreenSharingState) {
+ currentVideoConstraint = false;
+ currentAudioConstraint = false;
+ }
+
+ // Disable screen sharing when in a Jitsi
+ if (!$gameOverlayVisibilityStore) {
+ currentVideoConstraint = false;
+ currentAudioConstraint = false;
+ }
+
+ // Disable screen sharing if no peers
+ if ($peerStore.size === 0) {
+ currentVideoConstraint = false;
+ currentAudioConstraint = false;
+ }
+
+ // Let's make the changes only if the new value is different from the old one.
+ if (previousComputedVideoConstraint != currentVideoConstraint || previousComputedAudioConstraint != currentAudioConstraint) {
+ previousComputedVideoConstraint = currentVideoConstraint;
+ previousComputedAudioConstraint = currentAudioConstraint;
+ // Let's copy the objects.
+ /*if (typeof previousComputedVideoConstraint !== 'boolean') {
+ previousComputedVideoConstraint = {...previousComputedVideoConstraint};
+ }
+ if (typeof previousComputedAudioConstraint !== 'boolean') {
+ previousComputedAudioConstraint = {...previousComputedAudioConstraint};
+ }*/
+
+ set({
+ video: currentVideoConstraint,
+ audio: currentAudioConstraint,
+ });
+ }
+ }, {
+ video: false,
+ audio: false
+ } as MediaStreamConstraints);
+
+
+/**
+ * A store containing the MediaStream object for ScreenSharing (or null if nothing requested, or Error if an error occurred)
+ */
+export const screenSharingLocalStreamStore = derived, LocalStreamStoreValue>(screenSharingConstraintsStore, ($screenSharingConstraintsStore, set) => {
+ const constraints = $screenSharingConstraintsStore;
+
+ if ($screenSharingConstraintsStore.video === false && $screenSharingConstraintsStore.audio === false) {
+ stopScreenSharing();
+ requestedScreenSharingState.disableScreenSharing();
+ set({
+ type: 'success',
+ stream: null,
+ constraints
+ });
+ return;
+ }
+
+ let currentStreamPromise: Promise;
+ if (navigator.getDisplayMedia) {
+ currentStreamPromise = navigator.getDisplayMedia({constraints});
+ } else if (navigator.mediaDevices.getDisplayMedia) {
+ currentStreamPromise = navigator.mediaDevices.getDisplayMedia({constraints});
+ } else {
+ stopScreenSharing();
+ set({
+ type: 'error',
+ error: new Error('Your browser does not support sharing screen'),
+ constraints
+ });
+ return;
+ }
+
+ (async () => {
+ try {
+ stopScreenSharing();
+ currentStream = await currentStreamPromise;
+
+ // If stream ends (for instance if user clicks the stop screen sharing button in the browser), let's close the view
+ for (const track of currentStream.getTracks()) {
+ track.onended = () => {
+ stopScreenSharing();
+ requestedScreenSharingState.disableScreenSharing();
+ previousComputedVideoConstraint = false;
+ previousComputedAudioConstraint = false;
+ set({
+ type: 'success',
+ stream: null,
+ constraints: {
+ video: false,
+ audio: false
+ }
+ });
+ };
+ }
+
+ set({
+ type: 'success',
+ stream: currentStream,
+ constraints
+ });
+ return;
+ } catch (e) {
+ currentStream = null;
+ console.info("Error. Unable to share screen.", e);
+ set({
+ type: 'error',
+ error: e,
+ constraints
+ });
+ }
+ })();
+});
+
+/**
+ * A store containing whether the screen sharing button should be displayed or hidden.
+ */
+export const screenSharingAvailableStore = derived(peerStore, ($peerStore, set) => {
+ if (!navigator.getDisplayMedia && !navigator.mediaDevices.getDisplayMedia) {
+ set(false);
+ return;
+ }
+
+ set($peerStore.size !== 0);
+});
diff --git a/front/src/WebRtc/JitsiFactory.ts b/front/src/WebRtc/JitsiFactory.ts
index 8ddbba7b..d2b9ebdd 100644
--- a/front/src/WebRtc/JitsiFactory.ts
+++ b/front/src/WebRtc/JitsiFactory.ts
@@ -1,6 +1,8 @@
import {JITSI_URL} from "../Enum/EnvironmentVariable";
import {mediaManager} from "./MediaManager";
import {coWebsiteManager} from "./CoWebsiteManager";
+import {requestedCameraState, requestedMicrophoneState} from "../Stores/MediaStore";
+import {get} from "svelte/store";
declare const window:any; // eslint-disable-line @typescript-eslint/no-explicit-any
interface jitsiConfigInterface {
@@ -10,10 +12,9 @@ interface jitsiConfigInterface {
}
const getDefaultConfig = () : jitsiConfigInterface => {
- const constraints = mediaManager.getConstraintRequestedByUser();
return {
- startWithAudioMuted: !constraints.audio,
- startWithVideoMuted: constraints.video === false,
+ startWithAudioMuted: !get(requestedMicrophoneState),
+ startWithVideoMuted: !get(requestedCameraState),
prejoinPageEnabled: false
}
}
@@ -72,7 +73,6 @@ class JitsiFactory {
private jitsiApi: any; // eslint-disable-line @typescript-eslint/no-explicit-any
private audioCallback = this.onAudioChange.bind(this);
private videoCallback = this.onVideoChange.bind(this);
- private previousConfigMeet! : jitsiConfigInterface;
private jitsiScriptLoaded: boolean = false;
/**
@@ -83,9 +83,6 @@ class JitsiFactory {
}
public start(roomName: string, playerName:string, jwt?: string, config?: object, interfaceConfig?: object, jitsiUrl?: string): void {
- //save previous config
- this.previousConfigMeet = getDefaultConfig();
-
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
@@ -134,27 +131,22 @@ class JitsiFactory {
this.jitsiApi.removeListener('audioMuteStatusChanged', this.audioCallback);
this.jitsiApi.removeListener('videoMuteStatusChanged', this.videoCallback);
this.jitsiApi?.dispose();
-
- //restore previous config
- if(this.previousConfigMeet?.startWithAudioMuted){
- await mediaManager.disableMicrophone();
- }else{
- await mediaManager.enableMicrophone();
- }
-
- if(this.previousConfigMeet?.startWithVideoMuted){
- await mediaManager.disableCamera();
- }else{
- await mediaManager.enableCamera();
- }
}
private onAudioChange({muted}: {muted: boolean}): void {
- this.previousConfigMeet.startWithAudioMuted = muted;
+ if (muted) {
+ requestedMicrophoneState.disableMicrophone();
+ } else {
+ requestedMicrophoneState.enableMicrophone();
+ }
}
private onVideoChange({muted}: {muted: boolean}): void {
- this.previousConfigMeet.startWithVideoMuted = muted;
+ if (muted) {
+ requestedCameraState.disableWebcam();
+ } else {
+ requestedCameraState.enableWebcam();
+ }
}
private async loadJitsiScript(domain: string): Promise {
diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts
index 565a1e9f..7b8416d5 100644
--- a/front/src/WebRtc/MediaManager.ts
+++ b/front/src/WebRtc/MediaManager.ts
@@ -6,10 +6,21 @@ import { localUserStore } from "../Connexion/LocalUserStore";
import type { UserSimplePeerInterface } from "./SimplePeer";
import { SoundMeter } from "../Phaser/Components/SoundMeter";
import { DISABLE_NOTIFICATIONS } from "../Enum/EnvironmentVariable";
+import {
+ gameOverlayVisibilityStore, localStreamStore,
+ mediaStreamConstraintsStore,
+ requestedCameraState,
+ requestedMicrophoneState
+} from "../Stores/MediaStore";
+import {
+ requestedScreenSharingState,
+ screenSharingAvailableStore,
+ screenSharingLocalStreamStore
+} from "../Stores/ScreenSharingStore";
declare const navigator: any; // eslint-disable-line @typescript-eslint/no-explicit-any
-let videoConstraint: boolean | MediaTrackConstraints = {
+const videoConstraint: boolean | MediaTrackConstraints = {
width: { min: 640, ideal: 1280, max: 1920 },
height: { min: 400, ideal: 720 },
frameRate: { ideal: localUserStore.getVideoQualityValue() },
@@ -31,7 +42,6 @@ export type ReportCallback = (message: string) => void;
export type ShowReportCallBack = (userId: string, userName: string | undefined) => void;
export type HelpCameraSettingsCallBack = () => void;
-// TODO: Split MediaManager in 2 classes: MediaManagerUI (in charge of HTML) and MediaManager (singleton in charge of the camera only)
export class MediaManager {
localStream: MediaStream | null = null;
localScreenCapture: MediaStream | null = null;
@@ -47,10 +57,7 @@ export class MediaManager {
//FIX ME SOUNDMETER: check stalability of sound meter calculation
//mySoundMeterElement: HTMLDivElement;
private webrtcOutAudio: HTMLAudioElement;
- constraintsMedia: MediaStreamConstraints = {
- audio: audioConstraint,
- video: videoConstraint
- };
+
updatedLocalStreamCallBacks: Set = new Set();
startScreenSharingCallBacks: Set = new Set();
stopScreenSharingCallBacks: Set = new Set();
@@ -61,10 +68,8 @@ export class MediaManager {
private cinemaBtn: HTMLDivElement;
private monitorBtn: HTMLDivElement;
- private previousConstraint: MediaStreamConstraints;
- private focused: boolean = true;
- private hasCamera = true;
+ private focused: boolean = true;
private triggerCloseJistiFrame: Map = new Map();
@@ -88,14 +93,12 @@ export class MediaManager {
this.microphoneClose.style.display = "none";
this.microphoneClose.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
- this.enableMicrophone();
- //update tracking
+ requestedMicrophoneState.enableMicrophone();
});
this.microphone = HtmlUtils.getElementByIdOrFail('microphone');
this.microphone.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
- this.disableMicrophone();
- //update tracking
+ requestedMicrophoneState.disableMicrophone();
});
this.cinemaBtn = HtmlUtils.getElementByIdOrFail('btn-video');
@@ -103,14 +106,12 @@ export class MediaManager {
this.cinemaClose.style.display = "none";
this.cinemaClose.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
- this.enableCamera();
- //update tracking
+ requestedCameraState.enableWebcam();
});
this.cinema = HtmlUtils.getElementByIdOrFail('cinema');
this.cinema.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
- this.disableCamera();
- //update tracking
+ requestedCameraState.disableWebcam();
});
this.monitorBtn = HtmlUtils.getElementByIdOrFail('btn-monitor');
@@ -118,21 +119,20 @@ export class MediaManager {
this.monitorClose.style.display = "block";
this.monitorClose.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
- this.enableScreenSharing();
- //update tracking
+ //this.enableScreenSharing();
+ requestedScreenSharingState.enableScreenSharing();
});
this.monitor = HtmlUtils.getElementByIdOrFail('monitor');
this.monitor.style.display = "none";
this.monitor.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
- this.disableScreenSharing();
- //update tracking
+ //this.disableScreenSharing();
+ requestedScreenSharingState.disableScreenSharing();
});
- this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia));
this.pingCameraStatus();
- //FIX ME SOUNDMETER: check stalability of sound meter calculation
+ //FIX ME SOUNDMETER: check stability of sound meter calculation
/*this.mySoundMeterElement = (HtmlUtils.getElementByIdOrFail('mySoundMeter'));
this.mySoundMeterElement.childNodes.forEach((value: ChildNode, index) => {
this.mySoundMeterElement.children.item(index)?.classList.remove('active');
@@ -140,37 +140,98 @@ export class MediaManager {
//Check of ask notification navigator permission
this.getNotification();
+
+ localStreamStore.subscribe((result) => {
+ if (result.type === 'error') {
+ console.error(result.error);
+ layoutManager.addInformation('warning', 'Camera access denied. Click here and check navigators permissions.', () => {
+ this.showHelpCameraSettingsCallBack();
+ }, this.userInputManager);
+ return;
+ }
+
+ if (result.constraints.video !== false) {
+ HtmlUtils.getElementByIdOrFail('div-myCamVideo').classList.remove('hide');
+ } else {
+ HtmlUtils.getElementByIdOrFail('div-myCamVideo').classList.add('hide');
+ }/*
+ if (result.constraints.audio !== false) {
+ this.enableMicrophoneStyle();
+ } else {
+ this.disableMicrophoneStyle();
+ }*/
+
+ this.localStream = result.stream;
+ this.myCamVideo.srcObject = this.localStream;
+
+ // TODO: migrate all listeners to the store directly.
+ this.triggerUpdatedLocalStreamCallbacks(result.stream);
+ });
+
+ requestedCameraState.subscribe((enabled) => {
+ if (enabled) {
+ this.enableCameraStyle();
+ } else {
+ this.disableCameraStyle();
+ }
+ });
+ requestedMicrophoneState.subscribe((enabled) => {
+ if (enabled) {
+ this.enableMicrophoneStyle();
+ } else {
+ this.disableMicrophoneStyle();
+ }
+ });
+ //let screenSharingStream : MediaStream|null;
+ screenSharingLocalStreamStore.subscribe((result) => {
+ if (result.type === 'error') {
+ console.error(result.error);
+ layoutManager.addInformation('warning', 'Screen sharing denied. Click here and check navigators permissions.', () => {
+ this.showHelpCameraSettingsCallBack();
+ }, this.userInputManager);
+ return;
+ }
+
+ if (result.stream !== null) {
+ this.enableScreenSharingStyle();
+ mediaManager.localScreenCapture = result.stream;
+
+ // TODO: migrate this out of MediaManager
+ this.triggerStartedScreenSharingCallbacks(result.stream);
+
+ //screenSharingStream = result.stream;
+
+ this.addScreenSharingActiveVideo('me', DivImportance.Normal);
+ HtmlUtils.getElementByIdOrFail('screen-sharing-me').srcObject = result.stream;
+ } else {
+ this.disableScreenSharingStyle();
+ this.removeActiveScreenSharingVideo('me');
+
+ // FIXME: we need the old stream that is being stopped!
+ if (this.localScreenCapture) {
+ this.triggerStoppedScreenSharingCallbacks(this.localScreenCapture);
+ this.localScreenCapture = null;
+ }
+
+ //screenSharingStream = null;
+ }
+
+ });
+
+ screenSharingAvailableStore.subscribe((available) => {
+ if (available) {
+ document.querySelector('.btn-monitor')?.classList.remove('hide');
+ } else {
+ document.querySelector('.btn-monitor')?.classList.add('hide');
+ }
+ });
}
public updateScene(){
- //FIX ME SOUNDMETER: check stalability of sound meter calculation
+ //FIX ME SOUNDMETER: check stability of sound meter calculation
//this.updateSoudMeter();
}
- public blurCamera() {
- if (!this.focused) {
- return;
- }
- this.focused = false;
- this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia));
- this.disableCamera();
- }
-
- /**
- * Returns the constraint that the user wants (independently of the visibility / jitsi state...)
- */
- public getConstraintRequestedByUser(): MediaStreamConstraints {
- return this.previousConstraint ?? this.constraintsMedia;
- }
-
- public focusCamera() {
- if (this.focused) {
- return;
- }
- this.focused = true;
- this.applyPreviousConfig();
- }
-
public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void {
this.updatedLocalStreamCallBacks.add(callback);
}
@@ -214,6 +275,8 @@ export class MediaManager {
this.triggerCloseJitsiFrameButton();
}
buttonCloseFrame.removeEventListener('click', functionTrigger);
+
+ gameOverlayVisibilityStore.showGameOverlay();
}
public hideGameOverlay(): void {
@@ -225,110 +288,8 @@ export class MediaManager {
this.triggerCloseJitsiFrameButton();
}
buttonCloseFrame.addEventListener('click', functionTrigger);
- }
- public isGameOverlayVisible(): boolean {
- const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
- return gameOverlay.classList.contains('active');
- }
-
- public updateCameraQuality(value: number) {
- this.enableCameraStyle();
- const newVideoConstraint = JSON.parse(JSON.stringify(videoConstraint));
- newVideoConstraint.frameRate = { exact: value, ideal: value };
- videoConstraint = newVideoConstraint;
- this.constraintsMedia.video = videoConstraint;
- this.getCamera().then((stream: MediaStream) => {
- this.triggerUpdatedLocalStreamCallbacks(stream);
- });
- }
-
- public async enableCamera() {
- this.constraintsMedia.video = videoConstraint;
-
- try {
- const stream = await this.getCamera()
- //TODO show error message tooltip upper of camera button
- //TODO message : please check camera permission of your navigator
- if (stream.getVideoTracks().length === 0) {
- throw new Error('Video track is empty, please check camera permission of your navigator')
- }
- this.enableCameraStyle();
- this.triggerUpdatedLocalStreamCallbacks(stream);
- } catch(err) {
- console.error(err);
- this.disableCameraStyle();
- this.stopCamera();
-
- layoutManager.addInformation('warning', 'Camera access denied. Click here and check navigators permissions.', () => {
- this.showHelpCameraSettingsCallBack();
- }, this.userInputManager);
- }
- }
-
- public async disableCamera() {
- this.disableCameraStyle();
- this.stopCamera();
-
- if (this.constraintsMedia.audio !== false) {
- const stream = await this.getCamera();
- this.triggerUpdatedLocalStreamCallbacks(stream);
- } else {
- this.triggerUpdatedLocalStreamCallbacks(null);
- }
- }
-
- public async enableMicrophone() {
- this.constraintsMedia.audio = audioConstraint;
-
- try {
- const stream = await this.getCamera();
-
- //TODO show error message tooltip upper of camera button
- //TODO message : please check microphone permission of your navigator
- if (stream.getAudioTracks().length === 0) {
- throw Error('Audio track is empty, please check microphone permission of your navigator')
- }
- this.enableMicrophoneStyle();
- this.triggerUpdatedLocalStreamCallbacks(stream);
- } catch(err) {
- console.error(err);
- this.disableMicrophoneStyle();
-
- layoutManager.addInformation('warning', 'Microphone access denied. Click here and check navigators permissions.', () => {
- this.showHelpCameraSettingsCallBack();
- }, this.userInputManager);
- }
- }
-
- public async disableMicrophone() {
- this.disableMicrophoneStyle();
- this.stopMicrophone();
-
- if (this.constraintsMedia.video !== false) {
- const stream = await this.getCamera();
- this.triggerUpdatedLocalStreamCallbacks(stream);
- } else {
- this.triggerUpdatedLocalStreamCallbacks(null);
- }
- }
-
- private applyPreviousConfig() {
- this.constraintsMedia = this.previousConstraint;
- if (!this.constraintsMedia.video) {
- this.disableCameraStyle();
- } else {
- this.enableCameraStyle();
- }
- if (!this.constraintsMedia.audio) {
- this.disableMicrophoneStyle()
- } else {
- this.enableMicrophoneStyle()
- }
-
- this.getCamera().then((stream: MediaStream) => {
- this.triggerUpdatedLocalStreamCallbacks(stream);
- });
+ gameOverlayVisibilityStore.hideGameOverlay();
}
private enableCameraStyle() {
@@ -341,8 +302,6 @@ export class MediaManager {
this.cinemaClose.style.display = "block";
this.cinema.style.display = "none";
this.cinemaBtn.classList.add("disabled");
- this.constraintsMedia.video = false;
- this.myCamVideo.srcObject = null;
}
private enableMicrophoneStyle() {
@@ -355,185 +314,18 @@ export class MediaManager {
this.microphoneClose.style.display = "block";
this.microphone.style.display = "none";
this.microphoneBtn.classList.add("disabled");
- this.constraintsMedia.audio = false;
}
- private enableScreenSharing() {
- this.getScreenMedia().then((stream) => {
- this.triggerStartedScreenSharingCallbacks(stream);
- this.monitorClose.style.display = "none";
- this.monitor.style.display = "block";
- this.monitorBtn.classList.add("enabled");
- }, () => {
- this.monitorClose.style.display = "block";
- this.monitor.style.display = "none";
- this.monitorBtn.classList.remove("enabled");
-
- layoutManager.addInformation('warning', 'Screen sharing access denied. Click here and check navigators permissions.', () => {
- this.showHelpCameraSettingsCallBack();
- }, this.userInputManager);
- });
-
+ private enableScreenSharingStyle(){
+ this.monitorClose.style.display = "none";
+ this.monitor.style.display = "block";
+ this.monitorBtn.classList.add("enabled");
}
- private disableScreenSharing() {
+ private disableScreenSharingStyle(){
this.monitorClose.style.display = "block";
this.monitor.style.display = "none";
this.monitorBtn.classList.remove("enabled");
- this.removeActiveScreenSharingVideo('me');
- this.localScreenCapture?.getTracks().forEach((track: MediaStreamTrack) => {
- track.stop();
- });
- if (this.localScreenCapture === null) {
- console.warn('Weird: trying to remove a screen sharing that is not enabled');
- return;
- }
- const localScreenCapture = this.localScreenCapture;
- this.getCamera().then((stream) => {
- this.triggerStoppedScreenSharingCallbacks(localScreenCapture);
- }).catch((err) => { //catch error get camera
- console.error(err);
- this.triggerStoppedScreenSharingCallbacks(localScreenCapture);
- });
- this.localScreenCapture = null;
- }
-
- //get screen
- getScreenMedia(): Promise {
- try {
- return this._startScreenCapture()
- .then((stream: MediaStream) => {
- this.localScreenCapture = stream;
-
- // If stream ends (for instance if user clicks the stop screen sharing button in the browser), let's close the view
- for (const track of stream.getTracks()) {
- track.onended = () => {
- this.disableScreenSharing();
- };
- }
-
- this.addScreenSharingActiveVideo('me', DivImportance.Normal);
- HtmlUtils.getElementByIdOrFail('screen-sharing-me').srcObject = stream;
-
- return stream;
- })
- .catch((err: unknown) => {
- console.error("Error => getScreenMedia => ", err);
- throw err;
- });
- } catch (err) {
- return new Promise((resolve, reject) => { // eslint-disable-line no-unused-vars
- reject(err);
- });
- }
- }
-
- private _startScreenCapture() {
- if (navigator.getDisplayMedia) {
- return navigator.getDisplayMedia({ video: true });
- } else if (navigator.mediaDevices.getDisplayMedia) {
- return navigator.mediaDevices.getDisplayMedia({ video: true });
- } else {
- return new Promise((resolve, reject) => { // eslint-disable-line no-unused-vars
- reject("error sharing screen");
- });
- }
- }
-
- //get camera
- async getCamera(): Promise {
- if (navigator.mediaDevices === undefined) {
- if (window.location.protocol === 'http:') {
- throw new Error('Unable to access your camera or microphone. You need to use a HTTPS connection.');
- } else {
- throw new Error('Unable to access your camera or microphone. Your browser is too old.');
- }
- }
-
- return this.getLocalStream().catch((err) => {
- console.info('Error get camera, trying with video option at null =>', err);
- this.disableCameraStyle();
- this.stopCamera();
-
- return this.getLocalStream().then((stream: MediaStream) => {
- this.hasCamera = false;
- return stream;
- }).catch((err) => {
- this.disableMicrophoneStyle();
- console.info("error get media ", this.constraintsMedia.video, this.constraintsMedia.audio, err);
- throw err;
- });
- });
-
- //TODO resize remote cam
- /*console.log(this.localStream.getTracks());
- let videoMediaStreamTrack = this.localStream.getTracks().find((media : MediaStreamTrack) => media.kind === "video");
- let {width, height} = videoMediaStreamTrack.getSettings();
- console.info(`${width}x${height}`); // 6*/
- }
-
- private getLocalStream(): Promise {
- return navigator.mediaDevices.getUserMedia(this.constraintsMedia).then((stream: MediaStream) => {
- this.localStream = stream;
- this.myCamVideo.srcObject = this.localStream;
-
- //FIX ME SOUNDMETER: check stalability of sound meter calculation
- /*this.mySoundMeter = null;
- if(this.constraintsMedia.audio){
- this.mySoundMeter = new SoundMeter();
- this.mySoundMeter.connectToSource(stream, new AudioContext());
- }*/
- return stream;
- }).catch((err: Error) => {
- throw err;
- });
- }
-
- /**
- * Stops the camera from filming
- */
- public stopCamera(): void {
- if (this.localStream) {
- for (const track of this.localStream.getVideoTracks()) {
- track.stop();
- }
- }
- }
-
- /**
- * Stops the microphone from listening
- */
- public stopMicrophone(): void {
- if (this.localStream) {
- for (const track of this.localStream.getAudioTracks()) {
- track.stop();
- }
- }
- //this.mySoundMeter?.stop();
- }
-
- setCamera(id: string): Promise {
- let video = this.constraintsMedia.video;
- if (typeof (video) === 'boolean' || video === undefined) {
- video = {}
- }
- video.deviceId = {
- exact: id
- };
-
- return this.getCamera();
- }
-
- setMicrophone(id: string): Promise {
- let audio = this.constraintsMedia.audio;
- if (typeof (audio) === 'boolean' || audio === undefined) {
- audio = {}
- }
- audio.deviceId = {
- exact: id
- };
-
- return this.getCamera();
}
addActiveVideo(user: UserSimplePeerInterface, userName: string = "") {
diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts
index 67e72c6d..4633374d 100644
--- a/front/src/WebRtc/SimplePeer.ts
+++ b/front/src/WebRtc/SimplePeer.ts
@@ -14,6 +14,8 @@ import type {RoomConnection} from "../Connexion/RoomConnection";
import {connectionManager} from "../Connexion/ConnectionManager";
import {GameConnexionTypes} from "../Url/UrlManager";
import {blackListManager} from "./BlackListManager";
+import {get} from "svelte/store";
+import {localStreamStore, obtainedMediaConstraintStore} from "../Stores/MediaStore";
export interface UserSimplePeerInterface{
userId: number;
@@ -82,11 +84,10 @@ export class SimplePeer {
});
mediaManager.showGameOverlay();
- mediaManager.getCamera().finally(() => {
- //receive message start
- this.Connection.receiveWebrtcStart((message: UserSimplePeerInterface) => {
- this.receiveWebrtcStart(message);
- });
+
+ //receive message start
+ this.Connection.receiveWebrtcStart((message: UserSimplePeerInterface) => {
+ this.receiveWebrtcStart(message);
});
this.Connection.disconnectMessage((data: WebRtcDisconnectMessageInterface): void => {
@@ -344,8 +345,15 @@ export class SimplePeer {
if (!PeerConnection) {
throw new Error('While adding media, cannot find user with ID ' + userId);
}
- const localStream: MediaStream | null = mediaManager.localStream;
- PeerConnection.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...mediaManager.constraintsMedia})));
+
+ const result = get(localStreamStore);
+
+ PeerConnection.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...result.constraints})));
+
+ if (result.type === 'error') {
+ return;
+ }
+ const localStream: MediaStream | null = result.stream;
if(!localStream){
return;
diff --git a/front/src/WebRtc/VideoPeer.ts b/front/src/WebRtc/VideoPeer.ts
index 503ca0de..32e8e97f 100644
--- a/front/src/WebRtc/VideoPeer.ts
+++ b/front/src/WebRtc/VideoPeer.ts
@@ -5,6 +5,8 @@ import type {RoomConnection} from "../Connexion/RoomConnection";
import {blackListManager} from "./BlackListManager";
import type {Subscription} from "rxjs";
import type {UserSimplePeerInterface} from "./SimplePeer";
+import {get} from "svelte/store";
+import {obtainedMediaConstraintStore} from "../Stores/MediaStore";
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
@@ -191,7 +193,7 @@ export class VideoPeer extends Peer {
private pushVideoToRemoteUser() {
try {
const localStream: MediaStream | null = mediaManager.localStream;
- this.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...mediaManager.constraintsMedia})));
+ this.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...get(obtainedMediaConstraintStore)})));
if(!localStream){
return;
diff --git a/front/src/iframe_api.ts b/front/src/iframe_api.ts
index 8da1fa23..f76c4218 100644
--- a/front/src/iframe_api.ts
+++ b/front/src/iframe_api.ts
@@ -17,6 +17,10 @@ import { DataLayerEvent, isDataLayerEvent } from "./Api/Events/DataLayerEvent";
import type { ITiledMap } from "./Phaser/Map/ITiledMap";
import type { MenuItemRegisterEvent } from "./Api/Events/MenuItemRegisterEvent";
import { isMenuItemClickedEvent } from "./Api/Events/MenuItemClickedEvent";
+import type {PlaySoundEvent} from "./Api/Events/PlaySoundEvent";
+import type {StopSoundEvent} from "./Api/Events/StopSoundEvent";
+import type {LoadSoundEvent} from "./Api/Events/LoadSoundEvent";
+import SoundConfig = Phaser.Types.Sound.SoundConfig;
interface WorkAdventureApi {
sendChatMessage(message: string, author: string): void;
@@ -39,6 +43,7 @@ interface WorkAdventureApi {
restorePlayerControls(): void;
displayBubble(): void;
removeBubble(): void;
+ loadSound(url : string): Sound;
registerMenuCommand(commandDescriptor: string, callback: (commandDescriptor: string) => void): void
getCurrentUser(): Promise
getCurrentRoom(): Promise
@@ -91,7 +96,7 @@ interface ButtonDescriptor {
callback: ButtonClickedCallback,
}
-class Popup {
+export class Popup {
constructor(private id: number) {
}
@@ -108,12 +113,40 @@ class Popup {
}
}
-/*function uuidv4() {
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
- const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
- return v.toString(16);
- });
-}*/
+export class Sound {
+ constructor(private url: string) {
+ window.parent.postMessage({
+ "type" : 'loadSound',
+ "data": {
+ url: this.url,
+ } as LoadSoundEvent
+
+ },'*');
+ }
+
+ public play(config : SoundConfig) {
+ window.parent.postMessage({
+ "type" : 'playSound',
+ "data": {
+ url: this.url,
+ config
+ } as PlaySoundEvent
+
+ },'*');
+ return this.url;
+ }
+ public stop() {
+ window.parent.postMessage({
+ "type" : 'stopSound',
+ "data": {
+ url: this.url,
+ } as StopSoundEvent
+
+ },'*');
+ return this.url;
+ }
+
+}
function getGameState(): Promise {
if (immutableData) {
@@ -256,7 +289,11 @@ window.WA = {
}, '*');
},
- goToPage(url: string): void {
+ loadSound(url: string) : Sound {
+ return new Sound(url);
+ },
+
+ goToPage(url : string) : void{
window.parent.postMessage({
"type": 'goToPage',
"data": {
diff --git a/front/style/style.css b/front/style/style.css
index d95ac701..a3bbfa1d 100644
--- a/front/style/style.css
+++ b/front/style/style.css
@@ -143,6 +143,11 @@ body .message-info.warning{
bottom: 30px;
border-radius: 15px 15px 15px 15px;
max-height: 20%;
+ transition: right 350ms;
+}
+
+#div-myCamVideo.hide {
+ right: -20vw;
}
video#myCamVideo{
@@ -196,12 +201,12 @@ video#myCamVideo{
display: inline-flex;
bottom: 10px;
right: 15px;
- width: 15vw;
+ width: 180px;
height: 40px;
text-align: center;
align-content: center;
align-items: center;
- justify-content: center;
+ justify-content: flex-end;
justify-items: center;
}
/*btn animation*/
@@ -216,7 +221,6 @@ video#myCamVideo{
border-radius: 48px;
transform: translateY(20px);
transition-timing-function: ease-in-out;
- margin-bottom: 20px;
margin: 0 4%;
}
.btn-cam-action div.disabled {
@@ -248,6 +252,12 @@ video#myCamVideo{
transition: all .2s;
/*right: 224px;*/
}
+.btn-monitor.hide {
+ transform: translateY(60px);
+}
+.btn-cam-action:hover .btn-monitor.hide{
+ transform: translateY(60px);
+}
.btn-copy{
pointer-events: auto;
transition: all .3s;
@@ -346,6 +356,8 @@ video#myCamVideo{
#myCamVideoSetup {
width: 100%;
height: 100%;
+ -webkit-transform: scaleX(-1);
+ transform: scaleX(-1);
}
.webrtcsetup.active{
display: block;
diff --git a/maps/Tuto/scriptTuto.js b/maps/Tuto/scriptTuto.js
index 65962a94..8821134b 100644
--- a/maps/Tuto/scriptTuto.js
+++ b/maps/Tuto/scriptTuto.js
@@ -5,6 +5,12 @@ var targetObjectTutoBubble ='Tutobubble';
var targetObjectTutoChat ='tutoChat';
var targetObjectTutoExplanation ='tutoExplanation';
var popUpExplanation = undefined;
+var enterSoundUrl = "webrtc-in.mp3";
+var exitSoundUrl = "webrtc-out.mp3";
+var soundConfig = {
+ volume : 0.2,
+ loop : false
+}
function launchTuto (){
WA.openPopup(targetObjectTutoBubble, textFirstPopup, [
{
@@ -25,7 +31,8 @@ function launchTuto (){
label: "Got it!",
className : "success",callback:(popup2 => {
popup2.close();
- WA.restorePlayerControls();
+ WA.restorePlayerControl();
+ WA.loadSound(winSoundUrl).play(soundConfig);
})
}
])
@@ -36,13 +43,14 @@ function launchTuto (){
}
}
]);
- WA.disablePlayerControls();
+ WA.disablePlayerControl();
}
WA.onEnterZone('popupZone', () => {
WA.displayBubble();
+ WA.loadSound(enterSoundUrl).play(soundConfig);
if (!isFirstTimeTuto) {
isFirstTimeTuto = true;
launchTuto();
@@ -71,4 +79,5 @@ WA.onEnterZone('popupZone', () => {
WA.onLeaveZone('popupZone', () => {
if (popUpExplanation !== undefined) popUpExplanation.close();
WA.removeBubble();
+ WA.loadSound(exitSoundUrl).play(soundConfig);
})
diff --git a/maps/Tuto/webrtc-in.mp3 b/maps/Tuto/webrtc-in.mp3
new file mode 100644
index 00000000..34e22003
Binary files /dev/null and b/maps/Tuto/webrtc-in.mp3 differ
diff --git a/maps/Tuto/webrtc-out.mp3 b/maps/Tuto/webrtc-out.mp3
new file mode 100644
index 00000000..dcf02928
Binary files /dev/null and b/maps/Tuto/webrtc-out.mp3 differ
diff --git a/maps/Village/Village.json b/maps/Village/Village.json
index e4ee93bf..30733388 100644
--- a/maps/Village/Village.json
+++ b/maps/Village/Village.json
@@ -33,7 +33,7 @@
"y":0
},
{
- "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 1979, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 1979, 1979, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 1979, 0, 1979, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 0, 1979, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 1979, 0, 0, 1979, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 1979, 0, 1979, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 1979, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 1979, 0, 1979, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 0, 1979, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 0, 1979, 0, 0, 1979, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 1979, 1979, 0, 1979, 1979, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 1979, 1979, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 0, 1979, 0, 0, 0, 0, 0, 1979, 0, 1979, 0, 0, 1979, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 1979, 1979, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 1979, 0, 1979, 1979, 0, 1979, 1979, 0, 0, 0, 0, 1979, 1979, 1979, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 0, 1979, 0, 0, 0, 0, 0, 1979, 0, 1979, 0, 0, 1979, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 1979, 1979, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 1979, 1979, 0, 1979, 1979, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 1979, 1979, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 0, 1979, 0, 0, 1979, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 1979, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 1979, 1979, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 1979, 0, 1979, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 0, 1979, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 1979, 0, 0, 1979, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 1979, 0, 1979, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 1979, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 1979, 0, 1979, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 0, 1979, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 0, 1979, 0, 0, 1979, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 1979, 1979, 0, 1979, 1979, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 1979, 1979, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 0, 1979, 0, 0, 0, 0, 0, 1979, 0, 1979, 0, 0, 1979, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 1979, 1979, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 0, 1979, 0, 0, 0, 0, 0, 1979, 0, 1979, 0, 1979, 1979, 0, 1979, 1979, 0, 0, 0, 0, 1979, 1979, 1979, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 0, 1979, 0, 0, 0, 0, 0, 1979, 0, 1979, 0, 0, 1979, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 1979, 1979, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 1979, 1979, 0, 1979, 1979, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 1979, 1979, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 1979, 0, 1979, 0, 0, 1979, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 1979, 0, 0, 0, 0, 0, 0, 0, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":39,
"id":7,
"name":"collides",
@@ -769,7 +769,20 @@
"tilecount":121,
"tileheight":32,
"tilewidth":32
- },
+ },
+ {
+ "columns":3,
+ "firstgid":4611,
+ "image":"su1 Student fmale 12.png",
+ "imageheight":128,
+ "imagewidth":96,
+ "margin":0,
+ "name":"su1 Student fmale 12",
+ "spacing":0,
+ "tilecount":12,
+ "tileheight":32,
+ "tilewidth":32
+ },
{
"columns":5,
"firstgid":4611,
diff --git a/maps/tests/Audience.mp3 b/maps/tests/Audience.mp3
new file mode 100644
index 00000000..81745d14
Binary files /dev/null and b/maps/tests/Audience.mp3 differ
diff --git a/maps/tests/SoundScript.js b/maps/tests/SoundScript.js
new file mode 100644
index 00000000..f90dfe0f
--- /dev/null
+++ b/maps/tests/SoundScript.js
@@ -0,0 +1,44 @@
+var zonePlaySound = "PlaySound";
+var zonePlaySoundLoop = "playSoundLoop";
+var stopSound = "StopSound";
+var loopConfig ={
+ volume : 0.5,
+ loop : true
+}
+var configBase = {
+ volume : 0.5,
+ loop : false
+}
+var enterSoundUrl = "webrtc-in.mp3";
+var exitSoundUrl = "webrtc-out.mp3";
+var winSoundUrl = "Win.ogg";
+var enterSound;
+var exitSound;
+var winSound;
+loadAllSounds();
+winSound.play(configBase);
+WA.onEnterZone(zonePlaySound, () => {
+enterSound.play(configBase);
+})
+
+WA.onEnterZone(zonePlaySoundLoop, () => {
+winSound.play(loopConfig);
+})
+
+WA.onLeaveZone(zonePlaySoundLoop, () => {
+ winSound.stop();
+})
+
+WA.onEnterZone('popupZone', () => {
+
+});
+
+WA.onLeaveZone('popupZone', () => {
+
+})
+
+ function loadAllSounds(){
+ winSound = WA.loadSound(winSoundUrl);
+ enterSound = WA.loadSound(enterSoundUrl);
+ exitSound = WA.loadSound(exitSoundUrl);
+ }
diff --git a/maps/tests/SoundTest.json b/maps/tests/SoundTest.json
new file mode 100644
index 00000000..f1e38761
--- /dev/null
+++ b/maps/tests/SoundTest.json
@@ -0,0 +1,154 @@
+{ "compressionlevel":-1,
+ "height":20,
+ "infinite":false,
+ "layers":[
+ {
+ "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "height":20,
+ "id":2,
+ "name":"start",
+ "opacity":1,
+ "type":"tilelayer",
+ "visible":true,
+ "width":20,
+ "x":0,
+ "y":0
+ },
+ {
+ "data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
+ "height":20,
+ "id":4,
+ "name":"floor",
+ "opacity":1,
+ "type":"tilelayer",
+ "visible":true,
+ "width":20,
+ "x":0,
+ "y":0
+ },
+ {
+ "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "height":20,
+ "id":3,
+ "name":"playSound",
+ "opacity":1,
+ "properties":[
+ {
+ "name":"zone",
+ "type":"string",
+ "value":"PlaySound"
+ }],
+ "type":"tilelayer",
+ "visible":true,
+ "width":20,
+ "x":0,
+ "y":0
+ },
+ {
+ "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "height":20,
+ "id":6,
+ "name":"playSoundLoop",
+ "opacity":1,
+ "properties":[
+ {
+ "name":"zone",
+ "type":"string",
+ "value":"playSoundLoop"
+ }],
+ "type":"tilelayer",
+ "visible":true,
+ "width":20,
+ "x":0,
+ "y":0
+ },
+ {
+ "draworder":"topdown",
+ "id":5,
+ "name":"floorLayer",
+ "objects":[
+ {
+ "height":19.296875,
+ "id":2,
+ "name":"",
+ "rotation":0,
+ "text":
+ {
+ "text":"Play Sound",
+ "wrap":true
+ },
+ "type":"",
+ "visible":true,
+ "width":107.109375,
+ "x":258.4453125,
+ "y":197.018229166667
+ },
+ {
+ "height":19.296875,
+ "id":3,
+ "name":"",
+ "rotation":0,
+ "text":
+ {
+ "text":"Bonjour Monde",
+ "wrap":true
+ },
+ "type":"",
+ "visible":true,
+ "width":107.109375,
+ "x":-348.221354166667,
+ "y":257.018229166667
+ },
+ {
+ "height":55.296875,
+ "id":4,
+ "name":"",
+ "rotation":0,
+ "text":
+ {
+ "text":"Play Sound Loop\nexit Zone Stop Sound \n",
+ "wrap":true
+ },
+ "type":"",
+ "visible":true,
+ "width":176.442708333333,
+ "x":243.778645833333,
+ "y":368.3515625
+ }],
+ "opacity":1,
+ "type":"objectgroup",
+ "visible":true,
+ "x":0,
+ "y":0
+ }],
+ "nextlayerid":8,
+ "nextobjectid":5,
+ "orientation":"orthogonal",
+ "properties":[
+ {
+ "name":"script",
+ "type":"string",
+ "value":"SoundScript.js"
+ }],
+ "renderorder":"right-down",
+ "tiledversion":"1.5.0",
+ "tileheight":32,
+ "tilesets":[
+ {
+ "columns":11,
+ "firstgid":1,
+ "image":"tileset1.png",
+ "imageheight":352,
+ "imagewidth":352,
+ "margin":0,
+ "name":"tileset1",
+ "spacing":0,
+ "tilecount":121,
+ "tileheight":32,
+ "tilewidth":32
+ }],
+ "tilewidth":32,
+ "type":"map",
+ "version":1.5,
+ "width":20
+}
\ No newline at end of file
diff --git a/maps/tests/Win.ogg b/maps/tests/Win.ogg
new file mode 100644
index 00000000..43880a77
Binary files /dev/null and b/maps/tests/Win.ogg differ
diff --git a/maps/tests/index.html b/maps/tests/index.html
index 9b54a5af..a17a3b5d 100644
--- a/maps/tests/index.html
+++ b/maps/tests/index.html
@@ -42,6 +42,14 @@
Testing scripting API with a script
+
+
+ Success Failure Pending
+
+
+ Testing scripting API loadSound() function
+
+
Success Failure Pending
diff --git a/maps/tests/webrtc-in.mp3 b/maps/tests/webrtc-in.mp3
new file mode 100644
index 00000000..34e22003
Binary files /dev/null and b/maps/tests/webrtc-in.mp3 differ
diff --git a/maps/tests/webrtc-out.mp3 b/maps/tests/webrtc-out.mp3
new file mode 100644
index 00000000..dcf02928
Binary files /dev/null and b/maps/tests/webrtc-out.mp3 differ
diff --git a/maps/yarn.lock b/maps/yarn.lock
index ffb4747a..041c70ed 100644
--- a/maps/yarn.lock
+++ b/maps/yarn.lock
@@ -665,9 +665,9 @@ has@^1.0.3:
function-bind "^1.1.1"
hosted-git-info@^2.1.4:
- version "2.8.8"
- resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
- integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
+ version "2.8.9"
+ resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
+ integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
iconv-lite@^0.4.24:
version "0.4.24"
diff --git a/messages/yarn.lock b/messages/yarn.lock
index 81bd0ed1..af71c938 100644
--- a/messages/yarn.lock
+++ b/messages/yarn.lock
@@ -2097,9 +2097,9 @@ highlight.js@^9.12.0:
integrity sha512-zBZAmhSupHIl5sITeMqIJnYCDfAEc3Gdkqj65wC1lpI468MMQeeQkhcIAvk+RylAkxrCcI9xy9piHiXeQ1BdzQ==
hosted-git-info@^2.1.4, hosted-git-info@^2.7.1:
- version "2.8.8"
- resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
- integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
+ version "2.8.9"
+ resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
+ integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
html-tag@^2.0.0:
version "2.0.0"
diff --git a/pusher/yarn.lock b/pusher/yarn.lock
index 43f58988..8af760c8 100644
--- a/pusher/yarn.lock
+++ b/pusher/yarn.lock
@@ -1251,9 +1251,9 @@ has-values@^1.0.0:
kind-of "^4.0.0"
hosted-git-info@^2.1.4:
- version "2.8.8"
- resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
- integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
+ version "2.8.9"
+ resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
+ integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
http-errors@1.7.2:
version "1.7.2"
@@ -1704,9 +1704,9 @@ lodash.once@^4.0.0:
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19:
- version "4.17.20"
- resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
- integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
+ version "4.17.21"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+ integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
long@~3:
version "3.2.0"
diff --git a/uploader/yarn.lock b/uploader/yarn.lock
index 92253d44..5b6741ac 100644
--- a/uploader/yarn.lock
+++ b/uploader/yarn.lock
@@ -811,9 +811,9 @@ has@^1.0.3:
function-bind "^1.1.1"
hosted-git-info@^2.1.4:
- version "2.8.8"
- resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
- integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
+ version "2.8.9"
+ resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
+ integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
http-errors@1.7.2:
version "1.7.2"