This commit is contained in:
_Bastler 2021-05-11 20:18:12 +02:00
commit e9baef5963
10 changed files with 164 additions and 120 deletions

View File

@ -2,7 +2,7 @@ name: Build, push and deploy Docker image
on: on:
push: push:
branch: [master] branches: [master]
release: release:
types: [created] types: [created]
pull_request: pull_request:
@ -16,7 +16,7 @@ env:
jobs: jobs:
build-front: build-front:
if: ${{ github.event.release || github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }} if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -36,11 +36,11 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-front repository: thecodingmachine/workadventure-front
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || github.event.release && env.GITHUB_REF_SLUG || 'master' }} tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
add_git_labels: true add_git_labels: true
build-back: build-back:
if: ${{ github.event.release || github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }} if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -59,11 +59,11 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-back repository: thecodingmachine/workadventure-back
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || github.event.release && env.GITHUB_REF_SLUG || 'master' }} tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
add_git_labels: true add_git_labels: true
build-pusher: build-pusher:
if: ${{ github.event.release || github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }} if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -82,11 +82,11 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-pusher repository: thecodingmachine/workadventure-pusher
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || github.event.release && env.GITHUB_REF_SLUG || 'master' }} tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
add_git_labels: true add_git_labels: true
build-uploader: build-uploader:
if: ${{ github.event.release || github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }} if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -105,11 +105,11 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-uploader repository: thecodingmachine/workadventure-uploader
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || github.event.release && env.GITHUB_REF_SLUG || 'master' }} tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
add_git_labels: true add_git_labels: true
build-maps: build-maps:
if: ${{ github.event.release || github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }} if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -129,7 +129,7 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-maps repository: thecodingmachine/workadventure-maps
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || github.event.release && env.GITHUB_REF_SLUG || 'master' }} tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
add_git_labels: true add_git_labels: true
deeploy: deeploy:
@ -140,7 +140,7 @@ jobs:
- build-maps - build-maps
- build-uploader - build-uploader
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }} if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
steps: steps:
- name: Checkout - name: Checkout
@ -158,13 +158,13 @@ jobs:
JITSI_URL: ${{ secrets.JITSI_URL }} JITSI_URL: ${{ secrets.JITSI_URL }}
SECRET_JITSI_KEY: ${{ secrets.SECRET_JITSI_KEY }} SECRET_JITSI_KEY: ${{ secrets.SECRET_JITSI_KEY }}
TURN_STATIC_AUTH_SECRET: ${{ secrets.TURN_STATIC_AUTH_SECRET }} TURN_STATIC_AUTH_SECRET: ${{ secrets.TURN_STATIC_AUTH_SECRET }}
DEPLOY_REF: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || 'master' }} DEPLOY_REF: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
with: with:
namespace: workadventure-${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || 'master' }} namespace: workadventure-${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
- name: Add a comment in PR - name: Add a comment in PR
uses: unsplash/comment-on-pr@v1.2.0 uses: unsplash/comment-on-pr@v1.2.0
if: ${{ github.event.pull_request }} if: ${{ github.event_name == 'pull_request' }}
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:

View File

@ -2,7 +2,7 @@
local env = std.extVar("env"), local env = std.extVar("env"),
local namespace = env.DEPLOY_REF, local namespace = env.DEPLOY_REF,
local tag = namespace, local tag = namespace,
local url = if namespace == "master" then "workadventu.re" else namespace+".test.workadventu.re", local url = namespace+".test.workadventu.re",
// develop branch does not use admin because of issue with SSL certificate of admin as of now. // develop branch does not use admin because of issue with SSL certificate of admin as of now.
local adminUrl = if namespace == "master" || namespace == "develop" || std.startsWith(namespace, "admin") then "https://"+url else null, local adminUrl = if namespace == "master" || namespace == "develop" || std.startsWith(namespace, "admin") then "https://"+url else null,
"$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json", "$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json",
@ -25,10 +25,7 @@
"TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET, "TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET,
} + (if adminUrl != null then { } + (if adminUrl != null then {
"ADMIN_API_URL": adminUrl, "ADMIN_API_URL": adminUrl,
} else {}) + if namespace != "master" then { } else {})
// Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids!
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
}
}, },
"back2": { "back2": {
"image": "thecodingmachine/workadventure-back:"+tag, "image": "thecodingmachine/workadventure-back:"+tag,
@ -47,10 +44,7 @@
"TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET, "TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET,
} + (if adminUrl != null then { } + (if adminUrl != null then {
"ADMIN_API_URL": adminUrl, "ADMIN_API_URL": adminUrl,
} else {}) + if namespace != "master" then { } else {})
// Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids!
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
}
}, },
"pusher": { "pusher": {
"replicas": 2, "replicas": 2,
@ -69,10 +63,7 @@
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY, "SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
} + (if adminUrl != null then { } + (if adminUrl != null then {
"ADMIN_API_URL": adminUrl, "ADMIN_API_URL": adminUrl,
} else {}) + if namespace != "master" then { } else {})
// Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids!
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
}
}, },
"front": { "front": {
"image": "thecodingmachine/workadventure-front:"+tag, "image": "thecodingmachine/workadventure-front:"+tag,

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -17,14 +17,12 @@ export class SoundMeter {
} }
private init(context: AudioContext) { private init(context: AudioContext) {
if (this.context === undefined) { this.context = context;
this.context = context; this.analyser = this.context.createAnalyser();
this.analyser = this.context.createAnalyser();
this.analyser.fftSize = 2048; this.analyser.fftSize = 2048;
const bufferLength = this.analyser.fftSize; const bufferLength = this.analyser.fftSize;
this.dataArray = new Uint8Array(bufferLength); this.dataArray = new Uint8Array(bufferLength);
}
} }
public connectToSource(stream: MediaStream, context: AudioContext): void public connectToSource(stream: MediaStream, context: AudioContext): void

View File

@ -2,6 +2,7 @@ import {ResizableScene} from "../Login/ResizableScene";
import GameObject = Phaser.GameObjects.GameObject; import GameObject = Phaser.GameObjects.GameObject;
import Events = Phaser.Scenes.Events; import Events = Phaser.Scenes.Events;
import AnimationEvents = Phaser.Animations.Events; import AnimationEvents = Phaser.Animations.Events;
import StructEvents = Phaser.Structs.Events;
/** /**
* A scene that can track its dirty/pristine state. * A scene that can track its dirty/pristine state.
@ -23,12 +24,11 @@ export abstract class DirtyScene extends ResizableScene {
} }
this.isAlreadyTracking = true; this.isAlreadyTracking = true;
const trackAnimationFunction = this.trackAnimation.bind(this); const trackAnimationFunction = this.trackAnimation.bind(this);
this.events.on(Events.ADDED_TO_SCENE, (gameObject: GameObject) => { this.sys.updateList.on(StructEvents.PROCESS_QUEUE_ADD, (gameObject: GameObject) => {
this.objectListChanged = true; this.objectListChanged = true;
gameObject.on(AnimationEvents.ANIMATION_UPDATE, trackAnimationFunction); gameObject.on(AnimationEvents.ANIMATION_UPDATE, trackAnimationFunction);
}); });
this.sys.updateList.on(StructEvents.PROCESS_QUEUE_REMOVE, (gameObject: GameObject) => {
this.events.on(Events.REMOVED_FROM_SCENE, (gameObject: GameObject) => {
this.objectListChanged = true; this.objectListChanged = true;
gameObject.removeListener(AnimationEvents.ANIMATION_UPDATE, trackAnimationFunction); gameObject.removeListener(AnimationEvents.ANIMATION_UPDATE, trackAnimationFunction);
}); });

View File

@ -187,6 +187,7 @@ export class GameScene extends DirtyScene implements CenterListener {
private originalMapUrl: string|undefined; private originalMapUrl: string|undefined;
private pinchManager: PinchManager|undefined; private pinchManager: PinchManager|undefined;
private mapTransitioning: boolean = false; //used to prevent transitions happenning at the same time. private mapTransitioning: boolean = false; //used to prevent transitions happenning at the same time.
private onVisibilityChangeCallback: () => void;
constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) { constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) {
super({ super({
@ -202,10 +203,11 @@ export class GameScene extends DirtyScene implements CenterListener {
this.createPromise = new Promise<void>((resolve, reject): void => { this.createPromise = new Promise<void>((resolve, reject): void => {
this.createPromiseResolve = resolve; this.createPromiseResolve = resolve;
}) });
this.connectionAnswerPromise = new Promise<RoomJoinedMessageInterface>((resolve, reject): void => { this.connectionAnswerPromise = new Promise<RoomJoinedMessageInterface>((resolve, reject): void => {
this.connectionAnswerPromiseResolve = resolve; this.connectionAnswerPromiseResolve = resolve;
}); });
this.onVisibilityChangeCallback = this.onVisibilityChange.bind(this);
} }
//hook preload scene //hook preload scene
@ -504,6 +506,8 @@ export class GameScene extends DirtyScene implements CenterListener {
if (!this.room.isDisconnected()) { if (!this.room.isDisconnected()) {
this.connect(); this.connect();
} }
document.addEventListener('visibilitychange', this.onVisibilityChangeCallback);
} }
/** /**
@ -625,6 +629,7 @@ export class GameScene extends DirtyScene implements CenterListener {
self.chatModeSprite.setVisible(false); self.chatModeSprite.setVisible(false);
self.openChatIcon.setVisible(false); self.openChatIcon.setVisible(false);
audioManager.restoreVolume(); audioManager.restoreVolume();
self.onVisibilityChange();
} }
} }
}) })
@ -947,6 +952,8 @@ ${escapedMessage}
for(const iframeEvents of this.iframeSubscriptionList){ for(const iframeEvents of this.iframeSubscriptionList){
iframeEvents.unsubscribe(); iframeEvents.unsubscribe();
} }
document.removeEventListener('visibilitychange', this.onVisibilityChangeCallback);
} }
private removeAllRemotePlayers(): void { private removeAllRemotePlayers(): void {
@ -1511,6 +1518,8 @@ ${escapedMessage}
mediaManager.addTriggerCloseJitsiFrameButton('close-jisi',() => { mediaManager.addTriggerCloseJitsiFrameButton('close-jisi',() => {
this.stopJitsi(); this.stopJitsi();
}); });
this.onVisibilityChange();
} }
public stopJitsi(): void { public stopJitsi(): void {
@ -1546,6 +1555,7 @@ ${escapedMessage}
openJitsiRoomFunction(); openJitsiRoomFunction();
}, this.userInputManager); }, this.userInputManager);
} }
this.onVisibilityChange();
} }
//todo: put this into an 'orchestrator' scene (EntryScene?) //todo: put this into an 'orchestrator' scene (EntryScene?)
@ -1585,4 +1595,20 @@ ${escapedMessage}
waScaleManager.zoomModifier *= zoomFactor; waScaleManager.zoomModifier *= zoomFactor;
this.updateCameraOffset(); 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();
}
}
}
} }

View File

@ -252,7 +252,6 @@ export class EnableCameraScene extends ResizableScene {
update(time: number, delta: number): void { update(time: number, delta: number): void {
this.soundMeterSprite.setVolume(this.soundMeter.getVolume()); this.soundMeterSprite.setVolume(this.soundMeter.getVolume());
mediaManager.updateScene();
this.centerXDomElement(this.enableCameraSceneElement, 300); this.centerXDomElement(this.enableCameraSceneElement, 300);
} }

View File

@ -10,9 +10,10 @@ interface jitsiConfigInterface {
} }
const getDefaultConfig = () : jitsiConfigInterface => { const getDefaultConfig = () : jitsiConfigInterface => {
const constraints = mediaManager.getConstraintRequestedByUser();
return { return {
startWithAudioMuted: !mediaManager.constraintsMedia.audio, startWithAudioMuted: !constraints.audio,
startWithVideoMuted: mediaManager.constraintsMedia.video === false, startWithVideoMuted: constraints.video === false,
prejoinPageEnabled: false prejoinPageEnabled: false
} }
} }
@ -71,7 +72,7 @@ class JitsiFactory {
private jitsiApi: any; // eslint-disable-line @typescript-eslint/no-explicit-any private jitsiApi: any; // eslint-disable-line @typescript-eslint/no-explicit-any
private audioCallback = this.onAudioChange.bind(this); private audioCallback = this.onAudioChange.bind(this);
private videoCallback = this.onVideoChange.bind(this); private videoCallback = this.onVideoChange.bind(this);
private previousConfigMeet? : jitsiConfigInterface; private previousConfigMeet! : jitsiConfigInterface;
private jitsiScriptLoaded: boolean = false; private jitsiScriptLoaded: boolean = false;
/** /**
@ -136,32 +137,24 @@ class JitsiFactory {
//restore previous config //restore previous config
if(this.previousConfigMeet?.startWithAudioMuted){ if(this.previousConfigMeet?.startWithAudioMuted){
mediaManager.disableMicrophone(); await mediaManager.disableMicrophone();
}else{ }else{
mediaManager.enableMicrophone(); await mediaManager.enableMicrophone();
} }
if(this.previousConfigMeet?.startWithVideoMuted){ if(this.previousConfigMeet?.startWithVideoMuted){
mediaManager.disableCamera(); await mediaManager.disableCamera();
}else{ }else{
mediaManager.enableCamera(); await mediaManager.enableCamera();
} }
} }
private onAudioChange({muted}: {muted: boolean}): void { private onAudioChange({muted}: {muted: boolean}): void {
if (muted && mediaManager.constraintsMedia.audio === true) { this.previousConfigMeet.startWithAudioMuted = muted;
mediaManager.disableMicrophone();
} else if(!muted && mediaManager.constraintsMedia.audio === false) {
mediaManager.enableMicrophone();
}
} }
private onVideoChange({muted}: {muted: boolean}): void { private onVideoChange({muted}: {muted: boolean}): void {
if (muted && mediaManager.constraintsMedia.video !== false) { this.previousConfigMeet.startWithVideoMuted = muted;
mediaManager.disableCamera();
} else if(!muted && mediaManager.constraintsMedia.video === false) {
mediaManager.enableCamera();
}
} }
private async loadJitsiScript(domain: string): Promise<void> { private async loadJitsiScript(domain: string): Promise<void> {

View File

@ -20,7 +20,7 @@ const audioConstraint: boolean|MediaTrackConstraints = {
//TODO: make these values configurable in the game settings menu and store them in localstorage //TODO: make these values configurable in the game settings menu and store them in localstorage
autoGainControl: false, autoGainControl: false,
echoCancellation: true, echoCancellation: true,
noiseSuppression: false noiseSuppression: true
}; };
export type UpdatedLocalStreamCallback = (media: MediaStream|null) => void; export type UpdatedLocalStreamCallback = (media: MediaStream|null) => void;
@ -43,7 +43,8 @@ export class MediaManager {
microphoneClose: HTMLImageElement; microphoneClose: HTMLImageElement;
microphone: HTMLImageElement; microphone: HTMLImageElement;
webrtcInAudio: HTMLAudioElement; webrtcInAudio: HTMLAudioElement;
mySoundMeterElement: HTMLDivElement; //FIX ME SOUNDMETER: check stalability of sound meter calculation
//mySoundMeterElement: HTMLDivElement;
private webrtcOutAudio: HTMLAudioElement; private webrtcOutAudio: HTMLAudioElement;
constraintsMedia : MediaStreamConstraints = { constraintsMedia : MediaStreamConstraints = {
audio: audioConstraint, audio: audioConstraint,
@ -54,7 +55,7 @@ export class MediaManager {
stopScreenSharingCallBacks : Set<StopScreenSharingCallback> = new Set<StopScreenSharingCallback>(); stopScreenSharingCallBacks : Set<StopScreenSharingCallback> = new Set<StopScreenSharingCallback>();
showReportModalCallBacks : Set<ShowReportCallBack> = new Set<ShowReportCallBack>(); showReportModalCallBacks : Set<ShowReportCallBack> = new Set<ShowReportCallBack>();
helpCameraSettingsCallBacks : Set<HelpCameraSettingsCallBack> = new Set<HelpCameraSettingsCallBack>(); helpCameraSettingsCallBacks : Set<HelpCameraSettingsCallBack> = new Set<HelpCameraSettingsCallBack>();
private microphoneBtn: HTMLDivElement; private microphoneBtn: HTMLDivElement;
private cinemaBtn: HTMLDivElement; private cinemaBtn: HTMLDivElement;
private monitorBtn: HTMLDivElement; private monitorBtn: HTMLDivElement;
@ -62,18 +63,16 @@ export class MediaManager {
private previousConstraint : MediaStreamConstraints; private previousConstraint : MediaStreamConstraints;
private focused : boolean = true; private focused : boolean = true;
private lastUpdateScene : Date = new Date();
private setTimeOutlastUpdateScene? : NodeJS.Timeout;
private hasCamera = true; private hasCamera = true;
private triggerCloseJistiFrame : Map<String, Function> = new Map<String, Function>(); private triggerCloseJistiFrame : Map<String, Function> = new Map<String, Function>();
private userInputManager?: UserInputManager; private userInputManager?: UserInputManager;
private mySoundMeter?: SoundMeter|null; //FIX ME SOUNDMETER: check stalability of sound meter calculation
/*private mySoundMeter?: SoundMeter|null;
private soundMeters: Map<string, SoundMeter> = new Map<string, SoundMeter>(); private soundMeters: Map<string, SoundMeter> = new Map<string, SoundMeter>();
private soundMeterElements: Map<string, HTMLDivElement> = new Map<string, HTMLDivElement>(); private soundMeterElements: Map<string, HTMLDivElement> = new Map<string, HTMLDivElement>();*/
constructor() { constructor() {
@ -132,17 +131,19 @@ export class MediaManager {
this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia)); this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia));
this.pingCameraStatus(); this.pingCameraStatus();
this.checkActiveUser(); //todo: desactivated in case of bug //FIX ME SOUNDMETER: check stalability of sound meter calculation
/*this.mySoundMeterElement = (HtmlUtils.getElementByIdOrFail('mySoundMeter'));
this.mySoundMeterElement = (HtmlUtils.getElementByIdOrFail('mySoundMeter'));
this.mySoundMeterElement.childNodes.forEach((value: ChildNode, index) => { this.mySoundMeterElement.childNodes.forEach((value: ChildNode, index) => {
this.mySoundMeterElement.children.item(index)?.classList.remove('active'); this.mySoundMeterElement.children.item(index)?.classList.remove('active');
}); });*/
//Check of ask notification navigator permission
this.getNotification();
} }
public updateScene(){ public updateScene(){
this.lastUpdateScene = new Date(); //FIX ME SOUNDMETER: check stalability of sound meter calculation
this.updateSoudMeter(); //this.updateSoudMeter();
} }
public blurCamera() { public blurCamera() {
@ -154,6 +155,13 @@ export class MediaManager {
this.disableCamera(); 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() { public focusCamera() {
if(this.focused){ if(this.focused){
return; return;
@ -196,7 +204,7 @@ export class MediaManager {
} }
} }
public showGameOverlay(){ public showGameOverlay(): void {
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay'); const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
gameOverlay.classList.add('active'); gameOverlay.classList.add('active');
@ -207,7 +215,7 @@ export class MediaManager {
buttonCloseFrame.removeEventListener('click', functionTrigger); buttonCloseFrame.removeEventListener('click', functionTrigger);
} }
public hideGameOverlay(){ public hideGameOverlay(): void {
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay'); const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
gameOverlay.classList.remove('active'); gameOverlay.classList.remove('active');
@ -218,6 +226,11 @@ export class MediaManager {
buttonCloseFrame.addEventListener('click', functionTrigger); buttonCloseFrame.addEventListener('click', functionTrigger);
} }
public isGameOverlayVisible(): boolean {
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
return gameOverlay.classList.contains('active');
}
public updateCameraQuality(value: number) { public updateCameraQuality(value: number) {
this.enableCameraStyle(); this.enableCameraStyle();
const newVideoConstraint = JSON.parse(JSON.stringify(videoConstraint)); const newVideoConstraint = JSON.parse(JSON.stringify(videoConstraint));
@ -229,29 +242,32 @@ export class MediaManager {
}); });
} }
public enableCamera() { public async enableCamera() {
this.constraintsMedia.video = videoConstraint; this.constraintsMedia.video = videoConstraint;
this.getCamera().then((stream: MediaStream) => { try {
const stream = await this.getCamera()
//TODO show error message tooltip upper of camera button //TODO show error message tooltip upper of camera button
//TODO message : please check camera permission of your navigator //TODO message : please check camera permission of your navigator
if(stream.getVideoTracks().length === 0) { if(stream.getVideoTracks().length === 0) {
throw Error('Video track is empty, please check camera permission of your navigator') throw new Error('Video track is empty, please check camera permission of your navigator')
} }
this.enableCameraStyle(); this.enableCameraStyle();
this.triggerUpdatedLocalStreamCallbacks(stream); this.triggerUpdatedLocalStreamCallbacks(stream);
}).catch((err) => { } catch(err) {
console.error(err); console.error(err);
this.disableCameraStyle(); this.disableCameraStyle();
this.stopCamera();
layoutManager.addInformation('warning', 'Camera access denied. Click here and check navigators permissions.', () => { layoutManager.addInformation('warning', 'Camera access denied. Click here and check navigators permissions.', () => {
this.showHelpCameraSettingsCallBack(); this.showHelpCameraSettingsCallBack();
}, this.userInputManager); }, this.userInputManager);
}); }
} }
public async disableCamera() { public async disableCamera() {
this.disableCameraStyle(); this.disableCameraStyle();
this.stopCamera();
if (this.constraintsMedia.audio !== false) { if (this.constraintsMedia.audio !== false) {
const stream = await this.getCamera(); const stream = await this.getCamera();
@ -261,25 +277,27 @@ export class MediaManager {
} }
} }
public enableMicrophone() { public async enableMicrophone() {
this.constraintsMedia.audio = audioConstraint; this.constraintsMedia.audio = audioConstraint;
this.getCamera().then((stream) => { try {
const stream = await this.getCamera();
//TODO show error message tooltip upper of camera button //TODO show error message tooltip upper of camera button
//TODO message : please check microphone permission of your navigator //TODO message : please check microphone permission of your navigator
if(stream.getAudioTracks().length === 0) { if (stream.getAudioTracks().length === 0) {
throw Error('Audio track is empty, please check microphone permission of your navigator') throw Error('Audio track is empty, please check microphone permission of your navigator')
} }
this.enableMicrophoneStyle(); this.enableMicrophoneStyle();
this.triggerUpdatedLocalStreamCallbacks(stream); this.triggerUpdatedLocalStreamCallbacks(stream);
}).catch((err) => { } catch(err) {
console.error(err); console.error(err);
this.disableMicrophoneStyle(); this.disableMicrophoneStyle();
layoutManager.addInformation('warning', 'Microphone access denied. Click here and check navigators permissions.', () => { layoutManager.addInformation('warning', 'Microphone access denied. Click here and check navigators permissions.', () => {
this.showHelpCameraSettingsCallBack(); this.showHelpCameraSettingsCallBack();
}, this.userInputManager); }, this.userInputManager);
}); }
} }
public async disableMicrophone() { public async disableMicrophone() {
@ -324,7 +342,6 @@ export class MediaManager {
this.cinemaBtn.classList.add("disabled"); this.cinemaBtn.classList.add("disabled");
this.constraintsMedia.video = false; this.constraintsMedia.video = false;
this.myCamVideo.srcObject = null; this.myCamVideo.srcObject = null;
this.stopCamera();
} }
private enableMicrophoneStyle(){ private enableMicrophoneStyle(){
@ -411,7 +428,7 @@ export class MediaManager {
} }
private _startScreenCapture() { private _startScreenCapture() {
if (navigator.getDisplayMedia) { if (navigator.getDisplayMedia) {
return navigator.getDisplayMedia({video: true}); return navigator.getDisplayMedia({video: true});
} else if (navigator.mediaDevices.getDisplayMedia) { } else if (navigator.mediaDevices.getDisplayMedia) {
return navigator.mediaDevices.getDisplayMedia({video: true}); return navigator.mediaDevices.getDisplayMedia({video: true});
@ -435,6 +452,8 @@ export class MediaManager {
return this.getLocalStream().catch((err) => { return this.getLocalStream().catch((err) => {
console.info('Error get camera, trying with video option at null =>', err); console.info('Error get camera, trying with video option at null =>', err);
this.disableCameraStyle(); this.disableCameraStyle();
this.stopCamera();
return this.getLocalStream().then((stream : MediaStream) => { return this.getLocalStream().then((stream : MediaStream) => {
this.hasCamera = false; this.hasCamera = false;
return stream; return stream;
@ -457,12 +476,12 @@ export class MediaManager {
this.localStream = stream; this.localStream = stream;
this.myCamVideo.srcObject = this.localStream; this.myCamVideo.srcObject = this.localStream;
//init sound meter //FIX ME SOUNDMETER: check stalability of sound meter calculation
this.mySoundMeter = null; /*this.mySoundMeter = null;
if(this.constraintsMedia.audio){ if(this.constraintsMedia.audio){
this.mySoundMeter = new SoundMeter(); this.mySoundMeter = new SoundMeter();
this.mySoundMeter.connectToSource(stream, new AudioContext()); this.mySoundMeter.connectToSource(stream, new AudioContext());
} }*/
return stream; return stream;
}).catch((err: Error) => { }).catch((err: Error) => {
throw err; throw err;
@ -489,7 +508,7 @@ export class MediaManager {
track.stop(); track.stop();
} }
} }
this.mySoundMeter?.stop(); //this.mySoundMeter?.stop();
} }
setCamera(id: string): Promise<MediaStream> { setCamera(id: string): Promise<MediaStream> {
@ -546,7 +565,7 @@ export class MediaManager {
`; `;
layoutManager.add(DivImportance.Normal, userId, html); layoutManager.add(DivImportance.Normal, userId, html);
this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail<HTMLVideoElement>(userId)); this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail<HTMLVideoElement>(userId));
//permit to create participant in discussion part //permit to create participant in discussion part
@ -564,7 +583,7 @@ export class MediaManager {
showReportUser(); showReportUser();
}); });
} }
addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important){ addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important){
userId = this.getScreenSharingId(userId); userId = this.getScreenSharingId(userId);
@ -590,7 +609,7 @@ export class MediaManager {
} }
element.classList.add('active') //todo: why does a method 'disable' add a class 'active'? element.classList.add('active') //todo: why does a method 'disable' add a class 'active'?
} }
enabledMicrophoneByUserId(userId: number){ enabledMicrophoneByUserId(userId: number){
const element = document.getElementById(`microphone-${userId}`); const element = document.getElementById(`microphone-${userId}`);
if(!element){ if(!element){
@ -598,7 +617,7 @@ export class MediaManager {
} }
element.classList.remove('active') //todo: why does a method 'enable' remove a class 'active'? element.classList.remove('active') //todo: why does a method 'enable' remove a class 'active'?
} }
disabledVideoByUserId(userId: number) { disabledVideoByUserId(userId: number) {
let element = document.getElementById(`${userId}`); let element = document.getElementById(`${userId}`);
if (element) { if (element) {
@ -609,7 +628,7 @@ export class MediaManager {
element.style.display = "block"; element.style.display = "block";
} }
} }
enabledVideoByUserId(userId: number){ enabledVideoByUserId(userId: number){
let element = document.getElementById(`${userId}`); let element = document.getElementById(`${userId}`);
if(element){ if(element){
@ -632,11 +651,12 @@ export class MediaManager {
} }
remoteVideo.srcObject = stream; remoteVideo.srcObject = stream;
//FIX ME SOUNDMETER: check stalability of sound meter calculation
//sound metter //sound metter
const soundMeter = new SoundMeter(); /*const soundMeter = new SoundMeter();
soundMeter.connectToSource(stream, new AudioContext()); soundMeter.connectToSource(stream, new AudioContext());
this.soundMeters.set(userId, soundMeter); this.soundMeters.set(userId, soundMeter);
this.soundMeterElements.set(userId, HtmlUtils.getElementByIdOrFail<HTMLImageElement>('soundMeter-'+userId)); this.soundMeterElements.set(userId, HtmlUtils.getElementByIdOrFail<HTMLImageElement>('soundMeter-'+userId));*/
} }
addStreamRemoteScreenSharing(userId: string, stream : MediaStream){ addStreamRemoteScreenSharing(userId: string, stream : MediaStream){
// In the case of screen sharing (going both ways), we may need to create the HTML element if it does not exist yet // In the case of screen sharing (going both ways), we may need to create the HTML element if it does not exist yet
@ -647,14 +667,15 @@ export class MediaManager {
this.addStreamRemoteVideo(this.getScreenSharingId(userId), stream); this.addStreamRemoteVideo(this.getScreenSharingId(userId), stream);
} }
removeActiveVideo(userId: string){ removeActiveVideo(userId: string){
layoutManager.remove(userId); layoutManager.remove(userId);
this.remoteVideo.delete(userId); this.remoteVideo.delete(userId);
this.soundMeters.get(userId)?.stop(); //FIX ME SOUNDMETER: check stalability of sound meter calculation
/*this.soundMeters.get(userId)?.stop();
this.soundMeters.delete(userId); this.soundMeters.delete(userId);
this.soundMeterElements.delete(userId); this.soundMeterElements.delete(userId);*/
//permit to remove user in discussion part //permit to remove user in discussion part
this.removeParticipant(userId); this.removeParticipant(userId);
@ -662,7 +683,7 @@ export class MediaManager {
removeActiveScreenSharingVideo(userId: string) { removeActiveScreenSharingVideo(userId: string) {
this.removeActiveVideo(this.getScreenSharingId(userId)) this.removeActiveVideo(this.getScreenSharingId(userId))
} }
playWebrtcOutSound(): void { playWebrtcOutSound(): void {
this.webrtcOutAudio.play(); this.webrtcOutAudio.play();
} }
@ -708,7 +729,7 @@ export class MediaManager {
const connnectingSpinnerDiv = element.getElementsByClassName('connecting-spinner').item(0) as HTMLDivElement|null; const connnectingSpinnerDiv = element.getElementsByClassName('connecting-spinner').item(0) as HTMLDivElement|null;
return connnectingSpinnerDiv; return connnectingSpinnerDiv;
} }
private getColorByString(str: String) : String|null { private getColorByString(str: String) : String|null {
let hash = 0; let hash = 0;
if (str.length === 0) return null; if (str.length === 0) return null;
@ -776,22 +797,6 @@ export class MediaManager {
this.userInputManager = userInputManager; this.userInputManager = userInputManager;
discussionManager.setUserInputManager(userInputManager); discussionManager.setUserInputManager(userInputManager);
} }
//check if user is active
private checkActiveUser(){
if(this.setTimeOutlastUpdateScene){
clearTimeout(this.setTimeOutlastUpdateScene);
}
this.setTimeOutlastUpdateScene = setTimeout(() => {
const now = new Date();
//if last update is more of 10 sec
if( (now.getTime() - this.lastUpdateScene.getTime()) > 10000) {
this.blurCamera();
}else{
this.focusCamera();
}
this.checkActiveUser();
}, this.focused ? 10000 : 1000);
}
public setShowReportModalCallBacks(callback: ShowReportCallBack){ public setShowReportModalCallBacks(callback: ShowReportCallBack){
this.showReportModalCallBacks.add(callback); this.showReportModalCallBacks.add(callback);
@ -807,11 +812,12 @@ export class MediaManager {
} }
} }
updateSoudMeter(){ //FIX ME SOUNDMETER: check stalability of sound meter calculation
/*updateSoudMeter(){
try{ try{
const volume = parseInt(((this.mySoundMeter ? this.mySoundMeter.getVolume() : 0) / 10).toFixed(0)); const volume = parseInt(((this.mySoundMeter ? this.mySoundMeter.getVolume() : 0) / 10).toFixed(0));
this.setVolumeSoundMeter(volume, this.mySoundMeterElement); this.setVolumeSoundMeter(volume, this.mySoundMeterElement);
for(const indexUserId of this.soundMeters.keys()){ for(const indexUserId of this.soundMeters.keys()){
const soundMeter = this.soundMeters.get(indexUserId); const soundMeter = this.soundMeters.get(indexUserId);
const soundMeterElement = this.soundMeterElements.get(indexUserId); const soundMeterElement = this.soundMeterElements.get(indexUserId);
@ -824,7 +830,7 @@ export class MediaManager {
}catch(err){ }catch(err){
//console.error(err); //console.error(err);
} }
} }*/
private setVolumeSoundMeter(volume: number, element: HTMLDivElement){ private setVolumeSoundMeter(volume: number, element: HTMLDivElement){
if(volume <= 0 && !element.classList.contains('active')){ if(volume <= 0 && !element.classList.contains('active')){
@ -847,6 +853,32 @@ export class MediaManager {
elementChildre.classList.add('active'); elementChildre.classList.add('active');
}); });
} }
public getNotification(){
//Get notification
if (window.Notification && Notification.permission !== "granted") {
Notification.requestPermission().catch((err) => {
console.error(`Notification permission error`, err);
});
}
}
public createNotification(userName: string){
if(this.focused){
return;
}
if (window.Notification && Notification.permission === "granted") {
const title = 'WorkAdventure';
const options = {
body: `Hi! ${userName} wants to discuss with you, don't be afraid!`,
icon: '/resources/logos/logo-WA-min.png',
image: '/resources/logos/logo-WA-min.png',
badge: '/resources/logos/logo-WA-min.png',
};
new Notification(title, options);
//new Notification(`Hi! ${userName} wants to discuss with you, don't be afraid!`);
}
}
} }
export const mediaManager = new MediaManager(); export const mediaManager = new MediaManager();

View File

@ -158,6 +158,11 @@ export class SimplePeer {
this.sendLocalScreenSharingStreamToUser(user.userId); this.sendLocalScreenSharingStreamToUser(user.userId);
} }
}); });
//Create a notification for first user in circle discussion
if(this.PeerConnectionArray.size === 0){
mediaManager.createNotification(user.name??'');
}
this.PeerConnectionArray.set(user.userId, peer); this.PeerConnectionArray.set(user.userId, peer);
for (const peerConnectionListener of this.peerConnectionListeners) { for (const peerConnectionListener of this.peerConnectionListeners) {