diff --git a/front/src/Stores/ScreenSharingStore.ts b/front/src/Stores/ScreenSharingStore.ts new file mode 100644 index 00000000..626327a2 --- /dev/null +++ b/front/src/Stores/ScreenSharingStore.ts @@ -0,0 +1,180 @@ +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 + }); + } + })(); +}); diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 9aabaa19..440d1977 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -12,6 +12,7 @@ import { requestedCameraState, requestedMicrophoneState } from "../Stores/MediaStore"; +import {requestedScreenSharingState, screenSharingLocalStreamStore} from "../Stores/ScreenSharingStore"; declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any @@ -112,13 +113,15 @@ export class MediaManager { this.monitorClose.style.display = "block"; this.monitorClose.addEventListener('click', (e: MouseEvent) => { e.preventDefault(); - this.enableScreenSharing(); + //this.enableScreenSharing(); + requestedScreenSharingState.enableScreenSharing(); }); this.monitor = HtmlUtils.getElementByIdOrFail('monitor'); this.monitor.style.display = "none"; this.monitor.addEventListener('click', (e: MouseEvent) => { e.preventDefault(); - this.disableScreenSharing(); + //this.disableScreenSharing(); + requestedScreenSharingState.disableScreenSharing(); }); this.pingCameraStatus(); @@ -173,6 +176,41 @@ export class MediaManager { 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; + } + + }); } public updateScene(){ @@ -264,109 +302,16 @@ export class MediaManager { this.microphoneBtn.classList.add("disabled"); } - 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"); - }); - } - } - - /** - * 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(); } addActiveVideo(user: UserSimplePeerInterface, userName: string = ""){