Migrating MediaManager game part to Svelte store

This commit is contained in:
David Négrier 2021-05-19 11:17:43 +02:00
parent 28d78a7988
commit 8af8ccd54b
9 changed files with 161 additions and 322 deletions

View File

@ -190,7 +190,6 @@ 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;
private emoteManager!: EmoteManager; private emoteManager!: EmoteManager;
constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) { constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) {
@ -211,7 +210,6 @@ export class GameScene extends DirtyScene implements CenterListener {
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
@ -516,8 +514,6 @@ export class GameScene extends DirtyScene implements CenterListener {
this.connect(); this.connect();
} }
document.addEventListener('visibilitychange', this.onVisibilityChangeCallback);
this.emoteManager = new EmoteManager(this); this.emoteManager = new EmoteManager(this);
} }
@ -641,7 +637,6 @@ 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();
} }
} }
}) })
@ -946,8 +941,6 @@ ${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 {
@ -1507,8 +1500,6 @@ ${escapedMessage}
mediaManager.addTriggerCloseJitsiFrameButton('close-jisi',() => { mediaManager.addTriggerCloseJitsiFrameButton('close-jisi',() => {
this.stopJitsi(); this.stopJitsi();
}); });
this.onVisibilityChange();
} }
public stopJitsi(): void { public stopJitsi(): void {
@ -1517,7 +1508,6 @@ ${escapedMessage}
mediaManager.showGameOverlay(); mediaManager.showGameOverlay();
mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi'); mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi');
this.onVisibilityChange();
} }
//todo: put this into an 'orchestrator' scene (EntryScene?) //todo: put this into an 'orchestrator' scene (EntryScene?)
@ -1557,20 +1547,4 @@ ${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

@ -2,6 +2,8 @@ import {mediaManager} from "../../WebRtc/MediaManager";
import {HtmlUtils} from "../../WebRtc/HtmlUtils"; import {HtmlUtils} from "../../WebRtc/HtmlUtils";
import {localUserStore} from "../../Connexion/LocalUserStore"; import {localUserStore} from "../../Connexion/LocalUserStore";
import {DirtyScene} from "../Game/DirtyScene"; import {DirtyScene} from "../Game/DirtyScene";
import {get} from "svelte/store";
import {requestedCameraState, requestedMicrophoneState} from "../../Stores/MediaStore";
export const HelpCameraSettingsSceneName = 'HelpCameraSettingsScene'; export const HelpCameraSettingsSceneName = 'HelpCameraSettingsScene';
const helpCameraSettings = 'helpCameraSettings'; 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(); this.openHelpCameraSettingsOpened();
localUserStore.setHelpCameraSettingsShown(); localUserStore.setHelpCameraSettingsShown();
} }

View File

@ -10,6 +10,7 @@ import {GameConnexionTypes} from "../../Url/UrlManager";
import {WarningContainer, warningContainerHtml, warningContainerKey} from "../Components/WarningContainer"; import {WarningContainer, warningContainerHtml, warningContainerKey} from "../Components/WarningContainer";
import {worldFullWarningStream} from "../../Connexion/WorldFullWarningStream"; import {worldFullWarningStream} from "../../Connexion/WorldFullWarningStream";
import {menuIconVisible} from "../../Stores/MenuStore"; import {menuIconVisible} from "../../Stores/MenuStore";
import {videoConstraintStore} from "../../Stores/MediaStore";
export const MenuSceneName = 'MenuScene'; export const MenuSceneName = 'MenuScene';
const gameMenuKey = 'gameMenu'; const gameMenuKey = 'gameMenu';
@ -324,7 +325,7 @@ export class MenuScene extends Phaser.Scene {
if (valueVideo !== this.videoQualityValue) { if (valueVideo !== this.videoQualityValue) {
this.videoQualityValue = valueVideo; this.videoQualityValue = valueVideo;
localUserStore.setVideoQualityValue(valueVideo); localUserStore.setVideoQualityValue(valueVideo);
mediaManager.updateCameraQuality(valueVideo); videoConstraintStore.setFrameRate(valueVideo);
} }
this.closeGameQualityMenu(); this.closeGameQualityMenu();
} }

View File

@ -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 {peerStore} from "./PeerStore";
import {localUserStore} from "../Connexion/LocalUserStore"; import {localUserStore} from "../Connexion/LocalUserStore";
import {ITiledMapGroupLayer, ITiledMapObjectLayer, ITiledMapTileLayer} from "../Phaser/Map/ITiledMap"; import {ITiledMapGroupLayer, ITiledMapObjectLayer, ITiledMapTileLayer} from "../Phaser/Map/ITiledMap";
@ -76,6 +76,40 @@ export const requestedMicrophoneState = createRequestedMicrophoneState();
export const gameOverlayVisibilityStore = createGameOverlayVisibilityStore(); export const gameOverlayVisibilityStore = createGameOverlayVisibilityStore();
export const enableCameraSceneVisibilityStore = createEnableCameraSceneVisibilityStore(); 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. * A store that contains video constraints.
*/ */
@ -87,22 +121,20 @@ function createVideoConstraintStore() {
facingMode: "user", facingMode: "user",
resizeMode: 'crop-and-scale', resizeMode: 'crop-and-scale',
aspectRatio: 1.777777778 aspectRatio: 1.777777778
} as boolean|MediaTrackConstraints); } as MediaTrackConstraints);
let selectedDeviceId = null;
return { return {
subscribe, subscribe,
setDeviceId: (deviceId: string) => update((constraints) => { setDeviceId: (deviceId: string) => update((constraints) => {
selectedDeviceId = deviceId;
if (typeof(constraints) === 'boolean') {
constraints = {}
}
constraints.deviceId = { constraints.deviceId = {
exact: selectedDeviceId exact: deviceId
}; };
return constraints;
}),
setFrameRate: (frameRate: number) => update((constraints) => {
constraints.frameRate = { ideal: frameRate };
return constraints; return constraints;
}) })
}; };
@ -145,6 +177,9 @@ export const audioConstraintStore = createAudioConstraintStore();
let timeout: NodeJS.Timeout; let timeout: NodeJS.Timeout;
let previousComputedVideoConstraint: boolean|MediaTrackConstraints = false;
let previousComputedAudioConstraint: boolean|MediaTrackConstraints = false;
/** /**
* A store containing the media constraints we want to apply. * A store containing the media constraints we want to apply.
*/ */
@ -152,24 +187,23 @@ export const mediaStreamConstraintsStore = derived(
[ [
requestedCameraState, requestedCameraState,
requestedMicrophoneState, requestedMicrophoneState,
visibilityStore,
gameOverlayVisibilityStore, gameOverlayVisibilityStore,
peerStore,
enableCameraSceneVisibilityStore, enableCameraSceneVisibilityStore,
videoConstraintStore, videoConstraintStore,
audioConstraintStore, audioConstraintStore,
privacyShutdownStore,
], ( ], (
[ [
$requestedCameraState, $requestedCameraState,
$requestedMicrophoneState, $requestedMicrophoneState,
$visibilityStore,
$gameOverlayVisibilityStore, $gameOverlayVisibilityStore,
$peerStore,
$enableCameraSceneVisibilityStore, $enableCameraSceneVisibilityStore,
$videoConstraintStore, $videoConstraintStore,
$audioConstraintStore, $audioConstraintStore,
$privacyShutdownStore,
], set ], set
) => { ) => {
let currentVideoConstraint: boolean|MediaTrackConstraints = $videoConstraintStore; let currentVideoConstraint: boolean|MediaTrackConstraints = $videoConstraintStore;
let currentAudioConstraint: boolean|MediaTrackConstraints = $audioConstraintStore; let currentAudioConstraint: boolean|MediaTrackConstraints = $audioConstraintStore;
@ -197,22 +231,35 @@ export const mediaStreamConstraintsStore = derived(
currentAudioConstraint = false; currentAudioConstraint = false;
} }
// Disable webcam if the game is not visible and we are talking to noone. // Disable webcam for privacy reasons (the game is not visible and we were talking to noone)
if ($visibilityStore === false && $peerStore.size === 0) { if ($privacyShutdownStore === true) {
currentVideoConstraint = false; currentVideoConstraint = false;
} }
if (timeout) { // Let's make the changes only if the new value is different from the old one.
clearTimeout(timeout); 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. if (timeout) {
timeout = setTimeout(() => { clearTimeout(timeout);
set({ }
video: currentVideoConstraint,
audio: currentAudioConstraint, // Let's wait a little bit to avoid sending too many constraint changes.
}); timeout = setTimeout(() => {
}, 100) set({
video: currentVideoConstraint,
audio: currentAudioConstraint,
});
}, 100);
}
}, { }, {
video: false, video: false,
audio: false audio: false
@ -289,6 +336,7 @@ export const localStreamStore = derived<Readable<MediaStreamConstraints>, LocalS
} }
if (constraints.audio === false && constraints.video === false) { if (constraints.audio === false && constraints.video === false) {
currentStream = null;
set({ set({
type: 'success', type: 'success',
stream: null, stream: null,
@ -299,6 +347,8 @@ export const localStreamStore = derived<Readable<MediaStreamConstraints>, LocalS
(async () => { (async () => {
try { try {
stopMicrophone();
stopCamera();
currentStream = await navigator.mediaDevices.getUserMedia(constraints); currentStream = await navigator.mediaDevices.getUserMedia(constraints);
set({ set({
type: 'success', type: 'success',
@ -358,3 +408,10 @@ export const localStreamStore = derived<Readable<MediaStreamConstraints>, 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;
});

View File

@ -17,12 +17,16 @@ function createPeerStore() {
set(users); set(users);
simplePeer.registerPeerConnectionListener({ simplePeer.registerPeerConnectionListener({
onConnect(user: UserSimplePeerInterface) { onConnect(user: UserSimplePeerInterface) {
users.set(user.userId, user); update(users => {
set(users); users.set(user.userId, user);
return users;
});
}, },
onDisconnect(userId: number) { onDisconnect(userId: number) {
users.delete(userId); update(users => {
set(users); users.delete(userId);
return users;
});
} }
}) })
} }

View File

@ -2,6 +2,7 @@ import {JITSI_URL} from "../Enum/EnvironmentVariable";
import {mediaManager} from "./MediaManager"; import {mediaManager} from "./MediaManager";
import {coWebsiteManager} from "./CoWebsiteManager"; import {coWebsiteManager} from "./CoWebsiteManager";
import {requestedCameraState, requestedMicrophoneState} from "../Stores/MediaStore"; import {requestedCameraState, requestedMicrophoneState} from "../Stores/MediaStore";
import {get} from "svelte/store";
declare const window:any; // eslint-disable-line @typescript-eslint/no-explicit-any declare const window:any; // eslint-disable-line @typescript-eslint/no-explicit-any
interface jitsiConfigInterface { interface jitsiConfigInterface {
@ -11,10 +12,9 @@ interface jitsiConfigInterface {
} }
const getDefaultConfig = () : jitsiConfigInterface => { const getDefaultConfig = () : jitsiConfigInterface => {
const constraints = mediaManager.getConstraintRequestedByUser();
return { return {
startWithAudioMuted: !constraints.audio, startWithAudioMuted: !get(requestedMicrophoneState),
startWithVideoMuted: constraints.video === false, startWithVideoMuted: !get(requestedCameraState),
prejoinPageEnabled: false prejoinPageEnabled: false
} }
} }
@ -73,7 +73,6 @@ 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 jitsiScriptLoaded: boolean = false; 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 { 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 => { coWebsiteManager.insertCoWebsite((async cowebsiteDiv => {
// Jitsi meet external API maintains some data in local storage // Jitsi meet external API maintains some data in local storage
// which is sent via the appData URL parameter when joining a // 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('audioMuteStatusChanged', this.audioCallback);
this.jitsiApi.removeListener('videoMuteStatusChanged', this.videoCallback); this.jitsiApi.removeListener('videoMuteStatusChanged', this.videoCallback);
this.jitsiApi?.dispose(); 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 { private onAudioChange({muted}: {muted: boolean}): void {
this.previousConfigMeet.startWithAudioMuted = muted; if (muted) {
requestedMicrophoneState.disableMicrophone();
} else {
requestedMicrophoneState.enableMicrophone();
}
} }
private onVideoChange({muted}: {muted: boolean}): void { private onVideoChange({muted}: {muted: boolean}): void {
this.previousConfigMeet.startWithVideoMuted = muted; if (muted) {
requestedCameraState.disableWebcam();
} else {
requestedCameraState.enableWebcam();
}
} }
private async loadJitsiScript(domain: string): Promise<void> { private async loadJitsiScript(domain: string): Promise<void> {

View File

@ -7,7 +7,7 @@ import type {UserSimplePeerInterface} from "./SimplePeer";
import {SoundMeter} from "../Phaser/Components/SoundMeter"; import {SoundMeter} from "../Phaser/Components/SoundMeter";
import {DISABLE_NOTIFICATIONS} from "../Enum/EnvironmentVariable"; import {DISABLE_NOTIFICATIONS} from "../Enum/EnvironmentVariable";
import { import {
gameOverlayVisibilityStore, gameOverlayVisibilityStore, localStreamStore,
mediaStreamConstraintsStore, mediaStreamConstraintsStore,
requestedCameraState, requestedCameraState,
requestedMicrophoneState requestedMicrophoneState
@ -15,7 +15,7 @@ import {
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any 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 }, width: { min: 640, ideal: 1280, max: 1920 },
height: { min: 400, ideal: 720 }, height: { min: 400, ideal: 720 },
frameRate: { ideal: localUserStore.getVideoQualityValue() }, 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 ShowReportCallBack = (userId: string, userName: string|undefined) => void;
export type HelpCameraSettingsCallBack = () => 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 { export class MediaManager {
localStream: MediaStream|null = null; localStream: MediaStream|null = null;
localScreenCapture: MediaStream|null = null; localScreenCapture: MediaStream|null = null;
@ -53,10 +52,6 @@ export class MediaManager {
//FIX ME SOUNDMETER: check stalability of sound meter calculation //FIX ME SOUNDMETER: check stalability of sound meter calculation
//mySoundMeterElement: HTMLDivElement; //mySoundMeterElement: HTMLDivElement;
private webrtcOutAudio: HTMLAudioElement; private webrtcOutAudio: HTMLAudioElement;
constraintsMedia : MediaStreamConstraints = {
audio: audioConstraint,
video: videoConstraint
};
updatedLocalStreamCallBacks : Set<UpdatedLocalStreamCallback> = new Set<UpdatedLocalStreamCallback>(); updatedLocalStreamCallBacks : Set<UpdatedLocalStreamCallback> = new Set<UpdatedLocalStreamCallback>();
startScreenSharingCallBacks : Set<StartScreenSharingCallback> = new Set<StartScreenSharingCallback>(); startScreenSharingCallBacks : Set<StartScreenSharingCallback> = new Set<StartScreenSharingCallback>();
stopScreenSharingCallBacks : Set<StopScreenSharingCallback> = new Set<StopScreenSharingCallback>(); stopScreenSharingCallBacks : Set<StopScreenSharingCallback> = new Set<StopScreenSharingCallback>();
@ -67,11 +62,8 @@ export class MediaManager {
private cinemaBtn: HTMLDivElement; private cinemaBtn: HTMLDivElement;
private monitorBtn: HTMLDivElement; private monitorBtn: HTMLDivElement;
private previousConstraint : MediaStreamConstraints;
private focused : boolean = true; private focused : boolean = 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;
@ -94,15 +86,11 @@ export class MediaManager {
this.microphoneClose.style.display = "none"; this.microphoneClose.style.display = "none";
this.microphoneClose.addEventListener('click', (e: MouseEvent) => { this.microphoneClose.addEventListener('click', (e: MouseEvent) => {
e.preventDefault(); e.preventDefault();
this.enableMicrophone();
//update tracking
requestedMicrophoneState.enableMicrophone(); requestedMicrophoneState.enableMicrophone();
}); });
this.microphone = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('microphone'); this.microphone = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('microphone');
this.microphone.addEventListener('click', (e: MouseEvent) => { this.microphone.addEventListener('click', (e: MouseEvent) => {
e.preventDefault(); e.preventDefault();
this.disableMicrophone();
//update tracking
requestedMicrophoneState.disableMicrophone(); requestedMicrophoneState.disableMicrophone();
}); });
@ -111,15 +99,11 @@ export class MediaManager {
this.cinemaClose.style.display = "none"; this.cinemaClose.style.display = "none";
this.cinemaClose.addEventListener('click', (e: MouseEvent) => { this.cinemaClose.addEventListener('click', (e: MouseEvent) => {
e.preventDefault(); e.preventDefault();
this.enableCamera();
//update tracking
requestedCameraState.enableWebcam(); requestedCameraState.enableWebcam();
}); });
this.cinema = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('cinema'); this.cinema = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('cinema');
this.cinema.addEventListener('click', (e: MouseEvent) => { this.cinema.addEventListener('click', (e: MouseEvent) => {
e.preventDefault(); e.preventDefault();
this.disableCamera();
//update tracking
requestedCameraState.disableWebcam(); requestedCameraState.disableWebcam();
}); });
@ -129,20 +113,17 @@ export class MediaManager {
this.monitorClose.addEventListener('click', (e: MouseEvent) => { this.monitorClose.addEventListener('click', (e: MouseEvent) => {
e.preventDefault(); e.preventDefault();
this.enableScreenSharing(); this.enableScreenSharing();
//update tracking
}); });
this.monitor = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('monitor'); this.monitor = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('monitor');
this.monitor.style.display = "none"; this.monitor.style.display = "none";
this.monitor.addEventListener('click', (e: MouseEvent) => { this.monitor.addEventListener('click', (e: MouseEvent) => {
e.preventDefault(); e.preventDefault();
this.disableScreenSharing(); this.disableScreenSharing();
//update tracking
}); });
this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia));
this.pingCameraStatus(); 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 = (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');
@ -150,37 +131,40 @@ export class MediaManager {
//Check of ask notification navigator permission //Check of ask notification navigator permission
this.getNotification(); 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(){ public updateScene(){
//FIX ME SOUNDMETER: check stalability of sound meter calculation //FIX ME SOUNDMETER: check stability of sound meter calculation
//this.updateSoudMeter(); //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 { public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void {
this.updatedLocalStreamCallBacks.add(callback); this.updatedLocalStreamCallBacks.add(callback);
} }
@ -241,110 +225,6 @@ export class MediaManager {
gameOverlayVisibilityStore.hideGameOverlay(); 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(){ private enableCameraStyle(){
this.cinemaClose.style.display = "none"; this.cinemaClose.style.display = "none";
this.cinemaBtn.classList.remove("disabled"); this.cinemaBtn.classList.remove("disabled");
@ -355,8 +235,6 @@ export class MediaManager {
this.cinemaClose.style.display = "block"; this.cinemaClose.style.display = "block";
this.cinema.style.display = "none"; this.cinema.style.display = "none";
this.cinemaBtn.classList.add("disabled"); this.cinemaBtn.classList.add("disabled");
this.constraintsMedia.video = false;
this.myCamVideo.srcObject = null;
} }
private enableMicrophoneStyle(){ private enableMicrophoneStyle(){
@ -369,7 +247,6 @@ export class MediaManager {
this.microphoneClose.style.display = "block"; this.microphoneClose.style.display = "block";
this.microphone.style.display = "none"; this.microphone.style.display = "none";
this.microphoneBtn.classList.add("disabled"); this.microphoneBtn.classList.add("disabled");
this.constraintsMedia.audio = false;
} }
private enableScreenSharing() { private enableScreenSharing() {
@ -403,12 +280,12 @@ export class MediaManager {
return; return;
} }
const localScreenCapture = this.localScreenCapture; const localScreenCapture = this.localScreenCapture;
this.getCamera().then((stream) => { //this.getCamera().then((stream) => {
this.triggerStoppedScreenSharingCallbacks(localScreenCapture); this.triggerStoppedScreenSharingCallbacks(localScreenCapture);
}).catch((err) => { //catch error get camera /*}).catch((err) => { //catch error get camera
console.error(err); console.error(err);
this.triggerStoppedScreenSharingCallbacks(localScreenCapture); this.triggerStoppedScreenSharingCallbacks(localScreenCapture);
}); });*/
this.localScreenCapture = null; this.localScreenCapture = null;
} }
@ -454,55 +331,6 @@ export class MediaManager {
} }
} }
//get camera
async getCamera(): Promise<MediaStream> {
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<MediaStream> {
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 * Stops the camera from filming
*/ */
@ -526,30 +354,6 @@ export class MediaManager {
//this.mySoundMeter?.stop(); //this.mySoundMeter?.stop();
} }
setCamera(id: string): Promise<MediaStream> {
let video = this.constraintsMedia.video;
if (typeof(video) === 'boolean' || video === undefined) {
video = {}
}
video.deviceId = {
exact: id
};
return this.getCamera();
}
setMicrophone(id: string): Promise<MediaStream> {
let audio = this.constraintsMedia.audio;
if (typeof(audio) === 'boolean' || audio === undefined) {
audio = {}
}
audio.deviceId = {
exact: id
};
return this.getCamera();
}
addActiveVideo(user: UserSimplePeerInterface, userName: string = ""){ addActiveVideo(user: UserSimplePeerInterface, userName: string = ""){
this.webrtcInAudio.play(); this.webrtcInAudio.play();
const userId = ''+user.userId const userId = ''+user.userId

View File

@ -14,6 +14,8 @@ import type {RoomConnection} from "../Connexion/RoomConnection";
import {connectionManager} from "../Connexion/ConnectionManager"; import {connectionManager} from "../Connexion/ConnectionManager";
import {GameConnexionTypes} from "../Url/UrlManager"; import {GameConnexionTypes} from "../Url/UrlManager";
import {blackListManager} from "./BlackListManager"; import {blackListManager} from "./BlackListManager";
import {get} from "svelte/store";
import {localStreamStore, obtainedMediaConstraintStore} from "../Stores/MediaStore";
export interface UserSimplePeerInterface{ export interface UserSimplePeerInterface{
userId: number; userId: number;
@ -82,11 +84,10 @@ export class SimplePeer {
}); });
mediaManager.showGameOverlay(); mediaManager.showGameOverlay();
mediaManager.getCamera().finally(() => {
//receive message start //receive message start
this.Connection.receiveWebrtcStart((message: UserSimplePeerInterface) => { this.Connection.receiveWebrtcStart((message: UserSimplePeerInterface) => {
this.receiveWebrtcStart(message); this.receiveWebrtcStart(message);
});
}); });
this.Connection.disconnectMessage((data: WebRtcDisconnectMessageInterface): void => { this.Connection.disconnectMessage((data: WebRtcDisconnectMessageInterface): void => {
@ -344,8 +345,15 @@ export class SimplePeer {
if (!PeerConnection) { if (!PeerConnection) {
throw new Error('While adding media, cannot find user with ID ' + userId); 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){ if(!localStream){
return; return;

View File

@ -5,6 +5,8 @@ import type {RoomConnection} from "../Connexion/RoomConnection";
import {blackListManager} from "./BlackListManager"; import {blackListManager} from "./BlackListManager";
import type {Subscription} from "rxjs"; import type {Subscription} from "rxjs";
import type {UserSimplePeerInterface} from "./SimplePeer"; import type {UserSimplePeerInterface} from "./SimplePeer";
import {get} from "svelte/store";
import {obtainedMediaConstraintStore} from "../Stores/MediaStore";
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer'); const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
@ -191,7 +193,7 @@ export class VideoPeer extends Peer {
private pushVideoToRemoteUser() { private pushVideoToRemoteUser() {
try { try {
const localStream: MediaStream | null = mediaManager.localStream; 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){ if(!localStream){
return; return;