Merge branch 'develop' of github.com:thecodingmachine/workadventure into metadataScriptingApi

This commit is contained in:
GRL 2021-05-26 17:22:37 +02:00
commit 5565ddd3f4
42 changed files with 1422 additions and 460 deletions

View file

@ -6,10 +6,21 @@ import { localUserStore } from "../Connexion/LocalUserStore";
import type { UserSimplePeerInterface } from "./SimplePeer";
import { SoundMeter } from "../Phaser/Components/SoundMeter";
import { DISABLE_NOTIFICATIONS } from "../Enum/EnvironmentVariable";
import {
gameOverlayVisibilityStore, localStreamStore,
mediaStreamConstraintsStore,
requestedCameraState,
requestedMicrophoneState
} from "../Stores/MediaStore";
import {
requestedScreenSharingState,
screenSharingAvailableStore,
screenSharingLocalStreamStore
} from "../Stores/ScreenSharingStore";
declare const navigator: any; // eslint-disable-line @typescript-eslint/no-explicit-any
let videoConstraint: boolean | MediaTrackConstraints = {
const videoConstraint: boolean | MediaTrackConstraints = {
width: { min: 640, ideal: 1280, max: 1920 },
height: { min: 400, ideal: 720 },
frameRate: { ideal: localUserStore.getVideoQualityValue() },
@ -31,7 +42,6 @@ export type ReportCallback = (message: string) => void;
export type ShowReportCallBack = (userId: string, userName: string | undefined) => void;
export type HelpCameraSettingsCallBack = () => void;
// TODO: Split MediaManager in 2 classes: MediaManagerUI (in charge of HTML) and MediaManager (singleton in charge of the camera only)
export class MediaManager {
localStream: MediaStream | null = null;
localScreenCapture: MediaStream | null = null;
@ -47,10 +57,7 @@ export class MediaManager {
//FIX ME SOUNDMETER: check stalability of sound meter calculation
//mySoundMeterElement: HTMLDivElement;
private webrtcOutAudio: HTMLAudioElement;
constraintsMedia: MediaStreamConstraints = {
audio: audioConstraint,
video: videoConstraint
};
updatedLocalStreamCallBacks: Set<UpdatedLocalStreamCallback> = new Set<UpdatedLocalStreamCallback>();
startScreenSharingCallBacks: Set<StartScreenSharingCallback> = new Set<StartScreenSharingCallback>();
stopScreenSharingCallBacks: Set<StopScreenSharingCallback> = new Set<StopScreenSharingCallback>();
@ -61,10 +68,8 @@ export class MediaManager {
private cinemaBtn: HTMLDivElement;
private monitorBtn: HTMLDivElement;
private previousConstraint: MediaStreamConstraints;
private focused: boolean = true;
private hasCamera = true;
private focused: boolean = true;
private triggerCloseJistiFrame: Map<String, Function> = new Map<String, Function>();
@ -88,14 +93,12 @@ export class MediaManager {
this.microphoneClose.style.display = "none";
this.microphoneClose.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
this.enableMicrophone();
//update tracking
requestedMicrophoneState.enableMicrophone();
});
this.microphone = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('microphone');
this.microphone.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
this.disableMicrophone();
//update tracking
requestedMicrophoneState.disableMicrophone();
});
this.cinemaBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-video');
@ -103,14 +106,12 @@ export class MediaManager {
this.cinemaClose.style.display = "none";
this.cinemaClose.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
this.enableCamera();
//update tracking
requestedCameraState.enableWebcam();
});
this.cinema = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('cinema');
this.cinema.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
this.disableCamera();
//update tracking
requestedCameraState.disableWebcam();
});
this.monitorBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-monitor');
@ -118,21 +119,20 @@ export class MediaManager {
this.monitorClose.style.display = "block";
this.monitorClose.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
this.enableScreenSharing();
//update tracking
//this.enableScreenSharing();
requestedScreenSharingState.enableScreenSharing();
});
this.monitor = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('monitor');
this.monitor.style.display = "none";
this.monitor.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
this.disableScreenSharing();
//update tracking
//this.disableScreenSharing();
requestedScreenSharingState.disableScreenSharing();
});
this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia));
this.pingCameraStatus();
//FIX ME SOUNDMETER: check stalability of sound meter calculation
//FIX ME SOUNDMETER: check stability of sound meter calculation
/*this.mySoundMeterElement = (HtmlUtils.getElementByIdOrFail('mySoundMeter'));
this.mySoundMeterElement.childNodes.forEach((value: ChildNode, index) => {
this.mySoundMeterElement.children.item(index)?.classList.remove('active');
@ -140,37 +140,98 @@ export class MediaManager {
//Check of ask notification navigator permission
this.getNotification();
localStreamStore.subscribe((result) => {
if (result.type === 'error') {
console.error(result.error);
layoutManager.addInformation('warning', 'Camera access denied. Click here and check navigators permissions.', () => {
this.showHelpCameraSettingsCallBack();
}, this.userInputManager);
return;
}
if (result.constraints.video !== false) {
HtmlUtils.getElementByIdOrFail('div-myCamVideo').classList.remove('hide');
} else {
HtmlUtils.getElementByIdOrFail('div-myCamVideo').classList.add('hide');
}/*
if (result.constraints.audio !== false) {
this.enableMicrophoneStyle();
} else {
this.disableMicrophoneStyle();
}*/
this.localStream = result.stream;
this.myCamVideo.srcObject = this.localStream;
// TODO: migrate all listeners to the store directly.
this.triggerUpdatedLocalStreamCallbacks(result.stream);
});
requestedCameraState.subscribe((enabled) => {
if (enabled) {
this.enableCameraStyle();
} else {
this.disableCameraStyle();
}
});
requestedMicrophoneState.subscribe((enabled) => {
if (enabled) {
this.enableMicrophoneStyle();
} else {
this.disableMicrophoneStyle();
}
});
//let screenSharingStream : MediaStream|null;
screenSharingLocalStreamStore.subscribe((result) => {
if (result.type === 'error') {
console.error(result.error);
layoutManager.addInformation('warning', 'Screen sharing denied. Click here and check navigators permissions.', () => {
this.showHelpCameraSettingsCallBack();
}, this.userInputManager);
return;
}
if (result.stream !== null) {
this.enableScreenSharingStyle();
mediaManager.localScreenCapture = result.stream;
// TODO: migrate this out of MediaManager
this.triggerStartedScreenSharingCallbacks(result.stream);
//screenSharingStream = result.stream;
this.addScreenSharingActiveVideo('me', DivImportance.Normal);
HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('screen-sharing-me').srcObject = result.stream;
} else {
this.disableScreenSharingStyle();
this.removeActiveScreenSharingVideo('me');
// FIXME: we need the old stream that is being stopped!
if (this.localScreenCapture) {
this.triggerStoppedScreenSharingCallbacks(this.localScreenCapture);
this.localScreenCapture = null;
}
//screenSharingStream = null;
}
});
screenSharingAvailableStore.subscribe((available) => {
if (available) {
document.querySelector('.btn-monitor')?.classList.remove('hide');
} else {
document.querySelector('.btn-monitor')?.classList.add('hide');
}
});
}
public updateScene(){
//FIX ME SOUNDMETER: check stalability of sound meter calculation
//FIX ME SOUNDMETER: check stability of sound meter calculation
//this.updateSoudMeter();
}
public blurCamera() {
if (!this.focused) {
return;
}
this.focused = false;
this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia));
this.disableCamera();
}
/**
* Returns the constraint that the user wants (independently of the visibility / jitsi state...)
*/
public getConstraintRequestedByUser(): MediaStreamConstraints {
return this.previousConstraint ?? this.constraintsMedia;
}
public focusCamera() {
if (this.focused) {
return;
}
this.focused = true;
this.applyPreviousConfig();
}
public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void {
this.updatedLocalStreamCallBacks.add(callback);
}
@ -214,6 +275,8 @@ export class MediaManager {
this.triggerCloseJitsiFrameButton();
}
buttonCloseFrame.removeEventListener('click', functionTrigger);
gameOverlayVisibilityStore.showGameOverlay();
}
public hideGameOverlay(): void {
@ -225,110 +288,8 @@ export class MediaManager {
this.triggerCloseJitsiFrameButton();
}
buttonCloseFrame.addEventListener('click', functionTrigger);
}
public isGameOverlayVisible(): boolean {
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
return gameOverlay.classList.contains('active');
}
public updateCameraQuality(value: number) {
this.enableCameraStyle();
const newVideoConstraint = JSON.parse(JSON.stringify(videoConstraint));
newVideoConstraint.frameRate = { exact: value, ideal: value };
videoConstraint = newVideoConstraint;
this.constraintsMedia.video = videoConstraint;
this.getCamera().then((stream: MediaStream) => {
this.triggerUpdatedLocalStreamCallbacks(stream);
});
}
public async enableCamera() {
this.constraintsMedia.video = videoConstraint;
try {
const stream = await this.getCamera()
//TODO show error message tooltip upper of camera button
//TODO message : please check camera permission of your navigator
if (stream.getVideoTracks().length === 0) {
throw new Error('Video track is empty, please check camera permission of your navigator')
}
this.enableCameraStyle();
this.triggerUpdatedLocalStreamCallbacks(stream);
} catch(err) {
console.error(err);
this.disableCameraStyle();
this.stopCamera();
layoutManager.addInformation('warning', 'Camera access denied. Click here and check navigators permissions.', () => {
this.showHelpCameraSettingsCallBack();
}, this.userInputManager);
}
}
public async disableCamera() {
this.disableCameraStyle();
this.stopCamera();
if (this.constraintsMedia.audio !== false) {
const stream = await this.getCamera();
this.triggerUpdatedLocalStreamCallbacks(stream);
} else {
this.triggerUpdatedLocalStreamCallbacks(null);
}
}
public async enableMicrophone() {
this.constraintsMedia.audio = audioConstraint;
try {
const stream = await this.getCamera();
//TODO show error message tooltip upper of camera button
//TODO message : please check microphone permission of your navigator
if (stream.getAudioTracks().length === 0) {
throw Error('Audio track is empty, please check microphone permission of your navigator')
}
this.enableMicrophoneStyle();
this.triggerUpdatedLocalStreamCallbacks(stream);
} catch(err) {
console.error(err);
this.disableMicrophoneStyle();
layoutManager.addInformation('warning', 'Microphone access denied. Click here and check navigators permissions.', () => {
this.showHelpCameraSettingsCallBack();
}, this.userInputManager);
}
}
public async disableMicrophone() {
this.disableMicrophoneStyle();
this.stopMicrophone();
if (this.constraintsMedia.video !== false) {
const stream = await this.getCamera();
this.triggerUpdatedLocalStreamCallbacks(stream);
} else {
this.triggerUpdatedLocalStreamCallbacks(null);
}
}
private applyPreviousConfig() {
this.constraintsMedia = this.previousConstraint;
if (!this.constraintsMedia.video) {
this.disableCameraStyle();
} else {
this.enableCameraStyle();
}
if (!this.constraintsMedia.audio) {
this.disableMicrophoneStyle()
} else {
this.enableMicrophoneStyle()
}
this.getCamera().then((stream: MediaStream) => {
this.triggerUpdatedLocalStreamCallbacks(stream);
});
gameOverlayVisibilityStore.hideGameOverlay();
}
private enableCameraStyle() {
@ -341,8 +302,6 @@ export class MediaManager {
this.cinemaClose.style.display = "block";
this.cinema.style.display = "none";
this.cinemaBtn.classList.add("disabled");
this.constraintsMedia.video = false;
this.myCamVideo.srcObject = null;
}
private enableMicrophoneStyle() {
@ -355,185 +314,18 @@ export class MediaManager {
this.microphoneClose.style.display = "block";
this.microphone.style.display = "none";
this.microphoneBtn.classList.add("disabled");
this.constraintsMedia.audio = false;
}
private enableScreenSharing() {
this.getScreenMedia().then((stream) => {
this.triggerStartedScreenSharingCallbacks(stream);
this.monitorClose.style.display = "none";
this.monitor.style.display = "block";
this.monitorBtn.classList.add("enabled");
}, () => {
this.monitorClose.style.display = "block";
this.monitor.style.display = "none";
this.monitorBtn.classList.remove("enabled");
layoutManager.addInformation('warning', 'Screen sharing access denied. Click here and check navigators permissions.', () => {
this.showHelpCameraSettingsCallBack();
}, this.userInputManager);
});
private enableScreenSharingStyle(){
this.monitorClose.style.display = "none";
this.monitor.style.display = "block";
this.monitorBtn.classList.add("enabled");
}
private disableScreenSharing() {
private disableScreenSharingStyle(){
this.monitorClose.style.display = "block";
this.monitor.style.display = "none";
this.monitorBtn.classList.remove("enabled");
this.removeActiveScreenSharingVideo('me');
this.localScreenCapture?.getTracks().forEach((track: MediaStreamTrack) => {
track.stop();
});
if (this.localScreenCapture === null) {
console.warn('Weird: trying to remove a screen sharing that is not enabled');
return;
}
const localScreenCapture = this.localScreenCapture;
this.getCamera().then((stream) => {
this.triggerStoppedScreenSharingCallbacks(localScreenCapture);
}).catch((err) => { //catch error get camera
console.error(err);
this.triggerStoppedScreenSharingCallbacks(localScreenCapture);
});
this.localScreenCapture = null;
}
//get screen
getScreenMedia(): Promise<MediaStream> {
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<HTMLVideoElement>('screen-sharing-me').srcObject = stream;
return stream;
})
.catch((err: unknown) => {
console.error("Error => getScreenMedia => ", err);
throw err;
});
} catch (err) {
return new Promise((resolve, reject) => { // eslint-disable-line no-unused-vars
reject(err);
});
}
}
private _startScreenCapture() {
if (navigator.getDisplayMedia) {
return navigator.getDisplayMedia({ video: true });
} else if (navigator.mediaDevices.getDisplayMedia) {
return navigator.mediaDevices.getDisplayMedia({ video: true });
} else {
return new Promise((resolve, reject) => { // eslint-disable-line no-unused-vars
reject("error sharing screen");
});
}
}
//get camera
async getCamera(): Promise<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
*/
public stopCamera(): void {
if (this.localStream) {
for (const track of this.localStream.getVideoTracks()) {
track.stop();
}
}
}
/**
* Stops the microphone from listening
*/
public stopMicrophone(): void {
if (this.localStream) {
for (const track of this.localStream.getAudioTracks()) {
track.stop();
}
}
//this.mySoundMeter?.stop();
}
setCamera(id: string): Promise<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 = "") {