diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 7bbf1226..95ec6689 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -190,7 +190,6 @@ export class GameScene extends DirtyScene implements CenterListener { private originalMapUrl: string|undefined; private pinchManager: PinchManager|undefined; 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) { @@ -211,7 +210,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 @@ -516,8 +514,6 @@ export class GameScene extends DirtyScene implements CenterListener { this.connect(); } - document.addEventListener('visibilitychange', this.onVisibilityChangeCallback); - this.emoteManager = new EmoteManager(this); } @@ -641,7 +637,6 @@ export class GameScene extends DirtyScene implements CenterListener { self.chatModeSprite.setVisible(false); self.openChatIcon.setVisible(false); audioManager.restoreVolume(); - self.onVisibilityChange(); } } }) @@ -946,8 +941,6 @@ ${escapedMessage} for(const iframeEvents of this.iframeSubscriptionList){ iframeEvents.unsubscribe(); } - - document.removeEventListener('visibilitychange', this.onVisibilityChangeCallback); } private removeAllRemotePlayers(): void { @@ -1507,8 +1500,6 @@ ${escapedMessage} mediaManager.addTriggerCloseJitsiFrameButton('close-jisi',() => { this.stopJitsi(); }); - - this.onVisibilityChange(); } public stopJitsi(): void { @@ -1517,7 +1508,6 @@ ${escapedMessage} mediaManager.showGameOverlay(); mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi'); - this.onVisibilityChange(); } //todo: put this into an 'orchestrator' scene (EntryScene?) @@ -1557,20 +1547,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/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 76bf520f..54fa395a 100644 --- a/front/src/Phaser/Menu/MenuScene.ts +++ b/front/src/Phaser/Menu/MenuScene.ts @@ -10,6 +10,7 @@ import {GameConnexionTypes} from "../../Url/UrlManager"; import {WarningContainer, warningContainerHtml, warningContainerKey} from "../Components/WarningContainer"; import {worldFullWarningStream} from "../../Connexion/WorldFullWarningStream"; import {menuIconVisible} from "../../Stores/MenuStore"; +import {videoConstraintStore} from "../../Stores/MediaStore"; export const MenuSceneName = 'MenuScene'; const gameMenuKey = 'gameMenu'; @@ -324,7 +325,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/Stores/MediaStore.ts b/front/src/Stores/MediaStore.ts index e0f351f2..3945c01d 100644 --- a/front/src/Stores/MediaStore.ts +++ b/front/src/Stores/MediaStore.ts @@ -1,4 +1,4 @@ -import {derived, Readable, readable, writable, Writable} from "svelte/store"; +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"; @@ -76,6 +76,40 @@ 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 that contains video constraints. */ @@ -87,22 +121,20 @@ function createVideoConstraintStore() { facingMode: "user", resizeMode: 'crop-and-scale', aspectRatio: 1.777777778 - } as boolean|MediaTrackConstraints); - - let selectedDeviceId = null; + } as MediaTrackConstraints); return { subscribe, setDeviceId: (deviceId: string) => update((constraints) => { - selectedDeviceId = deviceId; - - if (typeof(constraints) === 'boolean') { - constraints = {} - } constraints.deviceId = { - exact: selectedDeviceId + exact: deviceId }; + return constraints; + }), + setFrameRate: (frameRate: number) => update((constraints) => { + constraints.frameRate = { ideal: frameRate }; + return constraints; }) }; @@ -145,6 +177,9 @@ 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. */ @@ -152,24 +187,23 @@ export const mediaStreamConstraintsStore = derived( [ requestedCameraState, requestedMicrophoneState, - visibilityStore, gameOverlayVisibilityStore, - peerStore, enableCameraSceneVisibilityStore, videoConstraintStore, audioConstraintStore, + privacyShutdownStore, ], ( [ $requestedCameraState, $requestedMicrophoneState, - $visibilityStore, $gameOverlayVisibilityStore, - $peerStore, $enableCameraSceneVisibilityStore, $videoConstraintStore, $audioConstraintStore, + $privacyShutdownStore, ], set ) => { + let currentVideoConstraint: boolean|MediaTrackConstraints = $videoConstraintStore; let currentAudioConstraint: boolean|MediaTrackConstraints = $audioConstraintStore; @@ -197,22 +231,35 @@ export const mediaStreamConstraintsStore = derived( currentAudioConstraint = false; } - // Disable webcam if the game is not visible and we are talking to noone. - if ($visibilityStore === false && $peerStore.size === 0) { + // Disable webcam for privacy reasons (the game is not visible and we were talking to noone) + if ($privacyShutdownStore === true) { currentVideoConstraint = false; } - if (timeout) { - clearTimeout(timeout); - } + // 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}; + } - // Let's wait a little bit to avoid sending too many constraint changes. - timeout = setTimeout(() => { - set({ - video: currentVideoConstraint, - audio: currentAudioConstraint, - }); - }, 100) + 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 @@ -289,6 +336,7 @@ export const localStreamStore = derived, LocalS } if (constraints.audio === false && constraints.video === false) { + currentStream = null; set({ type: 'success', stream: null, @@ -299,6 +347,8 @@ export const localStreamStore = derived, LocalS (async () => { try { + stopMicrophone(); + stopCamera(); currentStream = await navigator.mediaDevices.getUserMedia(constraints); set({ type: 'success', @@ -358,3 +408,10 @@ export const localStreamStore = derived, LocalS } })(); }); + +/** + * 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 index 14d14754..a582e692 100644 --- a/front/src/Stores/PeerStore.ts +++ b/front/src/Stores/PeerStore.ts @@ -17,12 +17,16 @@ function createPeerStore() { set(users); simplePeer.registerPeerConnectionListener({ onConnect(user: UserSimplePeerInterface) { - users.set(user.userId, user); - set(users); + update(users => { + users.set(user.userId, user); + return users; + }); }, onDisconnect(userId: number) { - users.delete(userId); - set(users); + update(users => { + users.delete(userId); + return users; + }); } }) } diff --git a/front/src/WebRtc/JitsiFactory.ts b/front/src/WebRtc/JitsiFactory.ts index 4e70a4d2..d2b9ebdd 100644 --- a/front/src/WebRtc/JitsiFactory.ts +++ b/front/src/WebRtc/JitsiFactory.ts @@ -2,6 +2,7 @@ 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 { @@ -11,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 } } @@ -73,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; /** @@ -84,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 @@ -135,31 +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(); - requestedMicrophoneState.disableMicrophone(); - }else{ - await mediaManager.enableMicrophone(); - requestedMicrophoneState.enableMicrophone(); - } - - if(this.previousConfigMeet?.startWithVideoMuted){ - await mediaManager.disableCamera(); - requestedCameraState.disableWebcam(); - }else{ - await mediaManager.enableCamera(); - requestedCameraState.enableWebcam(); - } } 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 e604c50f..7c399e32 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -7,7 +7,7 @@ import type {UserSimplePeerInterface} from "./SimplePeer"; import {SoundMeter} from "../Phaser/Components/SoundMeter"; import {DISABLE_NOTIFICATIONS} from "../Enum/EnvironmentVariable"; import { - gameOverlayVisibilityStore, + gameOverlayVisibilityStore, localStreamStore, mediaStreamConstraintsStore, requestedCameraState, requestedMicrophoneState @@ -15,7 +15,7 @@ import { 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() }, @@ -37,7 +37,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; @@ -53,10 +52,6 @@ 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(); @@ -67,11 +62,8 @@ export class MediaManager { private cinemaBtn: HTMLDivElement; private monitorBtn: HTMLDivElement; - private previousConstraint : MediaStreamConstraints; private focused : boolean = true; - private hasCamera = true; - private triggerCloseJistiFrame : Map = new Map(); private userInputManager?: UserInputManager; @@ -94,15 +86,11 @@ 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(); }); @@ -111,15 +99,11 @@ 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(); }); @@ -129,20 +113,17 @@ export class MediaManager { this.monitorClose.addEventListener('click', (e: MouseEvent) => { e.preventDefault(); this.enableScreenSharing(); - //update tracking }); this.monitor = HtmlUtils.getElementByIdOrFail('monitor'); this.monitor.style.display = "none"; this.monitor.addEventListener('click', (e: MouseEvent) => { e.preventDefault(); this.disableScreenSharing(); - //update tracking }); - 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'); @@ -150,37 +131,40 @@ 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) { + this.enableCameraStyle(); + } else { + this.disableCameraStyle(); + } + 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); + }); } 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); } @@ -241,110 +225,6 @@ export class MediaManager { gameOverlayVisibilityStore.hideGameOverlay(); } - 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); - }); - } - private enableCameraStyle(){ this.cinemaClose.style.display = "none"; this.cinemaBtn.classList.remove("disabled"); @@ -355,8 +235,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(){ @@ -369,7 +247,6 @@ export class MediaManager { this.microphoneClose.style.display = "block"; this.microphone.style.display = "none"; this.microphoneBtn.classList.add("disabled"); - this.constraintsMedia.audio = false; } private enableScreenSharing() { @@ -403,12 +280,12 @@ export class MediaManager { return; } const localScreenCapture = this.localScreenCapture; - this.getCamera().then((stream) => { + //this.getCamera().then((stream) => { this.triggerStoppedScreenSharingCallbacks(localScreenCapture); - }).catch((err) => { //catch error get camera + /*}).catch((err) => { //catch error get camera console.error(err); this.triggerStoppedScreenSharingCallbacks(localScreenCapture); - }); + });*/ this.localScreenCapture = null; } @@ -454,55 +331,6 @@ export class MediaManager { } } - //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 */ @@ -526,30 +354,6 @@ export class MediaManager { //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 = ""){ this.webrtcInAudio.play(); const userId = ''+user.userId 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;