partey_workadventure/front/src/WebRtc/MediaManager.ts

886 lines
33 KiB
TypeScript
Raw Normal View History

import {DivImportance, layoutManager} from "./LayoutManager";
import {HtmlUtils} from "./HtmlUtils";
import {discussionManager, SendMessageCallback} from "./DiscussionManager";
import type {UserInputManager} from "../Phaser/UserInput/UserInputManager";
import {localUserStore} from "../Connexion/LocalUserStore";
import type {UserSimplePeerInterface} from "./SimplePeer";
import {SoundMeter} from "../Phaser/Components/SoundMeter";
import {DISABLE_NOTIFICATIONS} from "../Enum/EnvironmentVariable";
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
let videoConstraint: boolean|MediaTrackConstraints = {
width: { min: 640, ideal: 1280, max: 1920 },
height: { min: 400, ideal: 720 },
frameRate: { ideal: localUserStore.getVideoQualityValue() },
facingMode: "user",
resizeMode: 'crop-and-scale',
aspectRatio: 1.777777778
2020-05-03 14:29:45 +02:00
};
const audioConstraint: boolean|MediaTrackConstraints = {
//TODO: make these values configurable in the game settings menu and store them in localstorage
autoGainControl: false,
echoCancellation: true,
noiseSuppression: true
};
export type UpdatedLocalStreamCallback = (media: MediaStream|null) => void;
export type StartScreenSharingCallback = (media: MediaStream) => void;
export type StopScreenSharingCallback = (media: MediaStream) => void;
export type ReportCallback = (message: string) => void;
export type ShowReportCallBack = (userId: string, userName: string|undefined) => void;
export type HelpCameraSettingsCallBack = () => void;
2020-06-23 12:24:36 +02:00
// TODO: Split MediaManager in 2 classes: MediaManagerUI (in charge of HTML) and MediaManager (singleton in charge of the camera only)
export class MediaManager {
2020-06-03 11:55:31 +02:00
localStream: MediaStream|null = null;
localScreenCapture: MediaStream|null = null;
2020-06-10 12:15:25 +02:00
private remoteVideo: Map<string, HTMLVideoElement> = new Map<string, HTMLVideoElement>();
2020-06-03 11:55:31 +02:00
myCamVideo: HTMLVideoElement;
2020-06-10 12:15:25 +02:00
cinemaClose: HTMLImageElement;
cinema: HTMLImageElement;
monitorClose: HTMLImageElement;
monitor: HTMLImageElement;
2020-06-10 12:15:25 +02:00
microphoneClose: HTMLImageElement;
microphone: HTMLImageElement;
2020-06-03 11:55:31 +02:00
webrtcInAudio: HTMLAudioElement;
2021-05-10 20:49:17 +02:00
//FIX ME SOUNDMETER: check stalability of sound meter calculation
//mySoundMeterElement: HTMLDivElement;
private webrtcOutAudio: HTMLAudioElement;
2020-06-10 12:15:25 +02:00
constraintsMedia : MediaStreamConstraints = {
audio: audioConstraint,
2020-05-03 14:29:45 +02:00
video: videoConstraint
};
updatedLocalStreamCallBacks : Set<UpdatedLocalStreamCallback> = new Set<UpdatedLocalStreamCallback>();
startScreenSharingCallBacks : Set<StartScreenSharingCallback> = new Set<StartScreenSharingCallback>();
stopScreenSharingCallBacks : Set<StopScreenSharingCallback> = new Set<StopScreenSharingCallback>();
showReportModalCallBacks : Set<ShowReportCallBack> = new Set<ShowReportCallBack>();
helpCameraSettingsCallBacks : Set<HelpCameraSettingsCallBack> = new Set<HelpCameraSettingsCallBack>();
private microphoneBtn: HTMLDivElement;
private cinemaBtn: HTMLDivElement;
private monitorBtn: HTMLDivElement;
private previousConstraint : MediaStreamConstraints;
2020-10-26 22:39:52 +01:00
private focused : boolean = true;
private hasCamera = true;
2020-11-17 18:03:44 +01:00
private triggerCloseJistiFrame : Map<String, Function> = new Map<String, Function>();
private userInputManager?: UserInputManager;
2021-05-10 20:49:17 +02:00
//FIX ME SOUNDMETER: check stalability of sound meter calculation
/*private mySoundMeter?: SoundMeter|null;
private soundMeters: Map<string, SoundMeter> = new Map<string, SoundMeter>();
2021-05-10 20:49:17 +02:00
private soundMeterElements: Map<string, HTMLDivElement> = new Map<string, HTMLDivElement>();*/
constructor() {
2020-10-25 19:38:00 +01:00
this.myCamVideo = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideo');
this.webrtcInAudio = HtmlUtils.getElementByIdOrFail<HTMLAudioElement>('audio-webrtc-in');
this.webrtcOutAudio = HtmlUtils.getElementByIdOrFail<HTMLAudioElement>('audio-webrtc-out');
this.webrtcInAudio.volume = 0.2;
this.webrtcOutAudio.volume = 0.2;
2020-04-26 20:55:20 +02:00
2020-10-25 19:38:00 +01:00
this.microphoneBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-micro');
this.microphoneClose = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('microphone-close');
2020-05-03 17:19:42 +02:00
this.microphoneClose.style.display = "none";
2020-06-10 12:15:25 +02:00
this.microphoneClose.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
this.enableMicrophone();
//update tracking
});
2020-10-25 19:38:00 +01:00
this.microphone = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('microphone');
2020-06-10 12:15:25 +02:00
this.microphone.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
this.disableMicrophone();
//update tracking
});
2020-10-25 19:38:00 +01:00
this.cinemaBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-video');
this.cinemaClose = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('cinema-close');
2020-05-03 17:19:42 +02:00
this.cinemaClose.style.display = "none";
2020-06-10 12:15:25 +02:00
this.cinemaClose.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
this.enableCamera();
//update tracking
});
2020-10-25 19:38:00 +01:00
this.cinema = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('cinema');
2020-06-10 12:15:25 +02:00
this.cinema.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
this.disableCamera();
//update tracking
});
2020-10-25 19:38:00 +01:00
this.monitorBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-monitor');
this.monitorClose = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('monitor-close');
this.monitorClose.style.display = "block";
2020-08-18 14:59:50 +02:00
this.monitorClose.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
this.enableScreenSharing();
//update tracking
});
2020-10-25 19:38:00 +01:00
this.monitor = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('monitor');
this.monitor.style.display = "none";
2020-08-18 14:59:50 +02:00
this.monitor.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
this.disableScreenSharing();
//update tracking
});
2020-10-25 21:59:14 +01:00
this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia));
2020-10-24 14:40:51 +02:00
this.pingCameraStatus();
2020-10-26 22:39:52 +01:00
2021-05-10 20:49:17 +02:00
//FIX ME SOUNDMETER: check stalability of sound meter calculation
/*this.mySoundMeterElement = (HtmlUtils.getElementByIdOrFail('mySoundMeter'));
this.mySoundMeterElement.childNodes.forEach((value: ChildNode, index) => {
this.mySoundMeterElement.children.item(index)?.classList.remove('active');
2021-05-10 20:49:17 +02:00
});*/
//Check of ask notification navigator permission
this.getNotification();
2020-04-26 20:55:20 +02:00
}
public updateScene(){
2021-05-10 20:49:17 +02:00
//FIX ME SOUNDMETER: check stalability of sound meter calculation
//this.updateSoudMeter();
2020-10-26 22:39:52 +01:00
}
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;
}
2020-10-26 22:39:52 +01:00
public focusCamera() {
if(this.focused){
return;
}
this.focused = true;
this.applyPreviousConfig();
2020-04-26 20:55:20 +02:00
}
public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void {
this.updatedLocalStreamCallBacks.add(callback);
}
public onStartScreenSharing(callback: StartScreenSharingCallback): void {
this.startScreenSharingCallBacks.add(callback);
}
public onStopScreenSharing(callback: StopScreenSharingCallback): void {
this.stopScreenSharingCallBacks.add(callback);
}
removeUpdateLocalStreamEventListener(callback: UpdatedLocalStreamCallback): void {
this.updatedLocalStreamCallBacks.delete(callback);
}
private triggerUpdatedLocalStreamCallbacks(stream: MediaStream|null): void {
for (const callback of this.updatedLocalStreamCallBacks) {
callback(stream);
}
}
private triggerStartedScreenSharingCallbacks(stream: MediaStream): void {
for (const callback of this.startScreenSharingCallBacks) {
callback(stream);
}
}
private triggerStoppedScreenSharingCallbacks(stream: MediaStream): void {
for (const callback of this.stopScreenSharingCallBacks) {
callback(stream);
}
}
public showGameOverlay(): void {
2020-10-25 19:38:00 +01:00
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
gameOverlay.classList.add('active');
2020-11-17 18:03:44 +01:00
const buttonCloseFrame = HtmlUtils.getElementByIdOrFail('cowebsite-close');
const functionTrigger = () => {
this.triggerCloseJitsiFrameButton();
}
buttonCloseFrame.removeEventListener('click', functionTrigger);
}
public hideGameOverlay(): void {
2020-10-25 19:38:00 +01:00
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
gameOverlay.classList.remove('active');
2020-11-17 18:03:44 +01:00
const buttonCloseFrame = HtmlUtils.getElementByIdOrFail('cowebsite-close');
const functionTrigger = () => {
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() {
2020-05-03 14:29:45 +02:00
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);
}
2020-04-26 20:55:20 +02:00
}
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");
this.cinema.style.display = "block";
}
private disableCameraStyle(){
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(){
this.microphoneClose.style.display = "none";
this.microphone.style.display = "block";
this.microphoneBtn.classList.remove("disabled");
}
private disableMicrophoneStyle(){
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);
2021-04-19 15:16:56 +02:00
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);
});
2021-04-19 15:16:56 +02:00
}
private disableScreenSharing() {
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);
2021-01-07 09:42:11 +01:00
}).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;
})
2020-08-18 14:59:50 +02:00
.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
2020-06-25 10:43:27 +02:00
async getCamera(): Promise<MediaStream> {
if (navigator.mediaDevices === undefined) {
2020-06-25 10:43:27 +02:00
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;
});
});
2020-06-25 10:43:27 +02:00
//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) => {
2020-06-25 10:43:27 +02:00
this.localStream = stream;
this.myCamVideo.srcObject = this.localStream;
2021-05-10 20:49:17 +02:00
//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());
2021-05-10 20:49:17 +02:00
}*/
2020-06-25 10:43:27 +02:00
return stream;
}).catch((err: Error) => {
2020-06-25 10:43:27 +02:00
throw err;
});
}
2020-04-25 20:29:03 +02:00
/**
* 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();
}
}
2021-05-10 20:49:17 +02:00
//this.mySoundMeter?.stop();
}
2020-06-23 12:24:36 +02:00
setCamera(id: string): Promise<MediaStream> {
let video = this.constraintsMedia.video;
if (typeof(video) === 'boolean' || video === undefined) {
video = {}
}
2020-06-25 10:33:26 +02:00
video.deviceId = {
exact: id
};
2020-06-23 12:24:36 +02:00
return this.getCamera();
}
setMicrophone(id: string): Promise<MediaStream> {
let audio = this.constraintsMedia.audio;
if (typeof(audio) === 'boolean' || audio === undefined) {
audio = {}
}
2020-06-25 11:26:55 +02:00
audio.deviceId = {
exact: id
};
2020-06-23 12:24:36 +02:00
return this.getCamera();
}
addActiveVideo(user: UserSimplePeerInterface, userName: string = ""){
this.webrtcInAudio.play();
const userId = ''+user.userId
userName = userName.toUpperCase();
2020-06-09 23:13:26 +02:00
const color = this.getColorByString(userName);
const html = `
<div id="div-${userId}" class="video-container">
<div class="connecting-spinner"></div>
<div class="rtc-error" style="display: none"></div>
<i id="name-${userId}" style="background-color: ${color};">${userName}</i>
<img id="microphone-${userId}" title="mute" src="resources/logos/microphone-close.svg">
<button id="report-${userId}" class="report">
<img title="report this user" src="resources/logos/report.svg">
<span>Report/Block</span>
</button>
<video id="${userId}" autoplay></video>
<img src="resources/logos/blockSign.svg" id="blocking-${userId}" class="block-logo">
<div id="soundMeter-${userId}" class="sound-progress">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
</div>
`;
layoutManager.add(DivImportance.Normal, userId, html);
2020-10-25 19:38:00 +01:00
this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail<HTMLVideoElement>(userId));
2020-10-25 21:59:14 +01:00
//permit to create participant in discussion part
const showReportUser = () => {
for(const callBack of this.showReportModalCallBacks){
callBack(userId, userName);
}
};
this.addNewParticipant(userId, userName, undefined, showReportUser);
const reportBanUserActionEl: HTMLImageElement = HtmlUtils.getElementByIdOrFail<HTMLImageElement>(`report-${userId}`);
reportBanUserActionEl.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
showReportUser();
});
}
addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important){
userId = this.getScreenSharingId(userId);
const html = `
<div id="div-${userId}" class="video-container">
<video id="${userId}" autoplay></video>
</div>
`;
layoutManager.add(divImportance, userId, html);
2020-10-25 19:38:00 +01:00
this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail<HTMLVideoElement>(userId));
}
private getScreenSharingId(userId: string): string {
return `screen-sharing-${userId}`;
}
disabledMicrophoneByUserId(userId: number){
2020-06-09 23:13:26 +02:00
const element = document.getElementById(`microphone-${userId}`);
if(!element){
return;
}
element.classList.add('active') //todo: why does a method 'disable' add a class 'active'?
}
enabledMicrophoneByUserId(userId: number){
2020-06-09 23:13:26 +02:00
const element = document.getElementById(`microphone-${userId}`);
if(!element){
return;
}
element.classList.remove('active') //todo: why does a method 'enable' remove a class 'active'?
}
disabledVideoByUserId(userId: number) {
let element = document.getElementById(`${userId}`);
2020-05-14 20:54:34 +02:00
if (element) {
element.style.opacity = "0";
}
element = document.getElementById(`name-${userId}`);
if (element) {
element.style.display = "block";
}
}
enabledVideoByUserId(userId: number){
let element = document.getElementById(`${userId}`);
2020-05-14 20:54:34 +02:00
if(element){
element.style.opacity = "1";
}
element = document.getElementById(`name-${userId}`);
if(element){
element.style.display = "none";
}
}
toggleBlockLogo(userId: number, show: boolean): void {
const blockLogoElement = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('blocking-'+userId);
show ? blockLogoElement.classList.add('active') : blockLogoElement.classList.remove('active');
}
addStreamRemoteVideo(userId: string, stream : MediaStream): void {
2020-06-10 12:15:25 +02:00
const remoteVideo = this.remoteVideo.get(userId);
if (remoteVideo === undefined) {
2020-10-20 18:02:44 +02:00
throw `Unable to find video for ${userId}`;
2020-06-10 12:15:25 +02:00
}
remoteVideo.srcObject = stream;
2021-05-10 20:49:17 +02:00
//FIX ME SOUNDMETER: check stalability of sound meter calculation
//sound metter
2021-05-10 20:49:17 +02:00
/*const soundMeter = new SoundMeter();
soundMeter.connectToSource(stream, new AudioContext());
this.soundMeters.set(userId, soundMeter);
2021-05-10 20:49:17 +02:00
this.soundMeterElements.set(userId, HtmlUtils.getElementByIdOrFail<HTMLImageElement>('soundMeter-'+userId));*/
2020-05-02 20:46:02 +02:00
}
addStreamRemoteScreenSharing(userId: string, stream : MediaStream){
// In the case of screen sharing (going both ways), we may need to create the HTML element if it does not exist yet
const remoteVideo = this.remoteVideo.get(this.getScreenSharingId(userId));
if (remoteVideo === undefined) {
this.addScreenSharingActiveVideo(userId);
}
this.addStreamRemoteVideo(this.getScreenSharingId(userId), stream);
2020-06-11 23:18:06 +02:00
}
removeActiveVideo(userId: string){
layoutManager.remove(userId);
2020-06-10 12:15:25 +02:00
this.remoteVideo.delete(userId);
2020-10-25 21:59:14 +01:00
2021-05-10 20:49:17 +02:00
//FIX ME SOUNDMETER: check stalability of sound meter calculation
/*this.soundMeters.get(userId)?.stop();
this.soundMeters.delete(userId);
2021-05-10 20:49:17 +02:00
this.soundMeterElements.delete(userId);*/
2020-10-25 21:59:14 +01:00
//permit to remove user in discussion part
this.removeParticipant(userId);
2020-04-25 20:29:03 +02:00
}
removeActiveScreenSharingVideo(userId: string) {
this.removeActiveVideo(this.getScreenSharingId(userId))
2020-06-11 23:18:06 +02:00
}
playWebrtcOutSound(): void {
this.webrtcOutAudio.play();
}
isConnecting(userId: string): void {
2020-06-09 23:13:26 +02:00
const connectingSpinnerDiv = this.getSpinner(userId);
if (connectingSpinnerDiv === null) {
return;
}
connectingSpinnerDiv.style.display = 'block';
}
isConnected(userId: string): void {
2020-06-09 23:13:26 +02:00
const connectingSpinnerDiv = this.getSpinner(userId);
if (connectingSpinnerDiv === null) {
return;
}
connectingSpinnerDiv.style.display = 'none';
}
isError(userId: string): void {
console.info("isError", `div-${userId}`);
2020-06-09 23:13:26 +02:00
const element = document.getElementById(`div-${userId}`);
if(!element){
return;
}
2020-06-09 23:13:26 +02:00
const errorDiv = element.getElementsByClassName('rtc-error').item(0) as HTMLDivElement|null;
if (errorDiv === null) {
return;
}
errorDiv.style.display = 'block';
}
isErrorScreenSharing(userId: string): void {
this.isError(this.getScreenSharingId(userId));
}
private getSpinner(userId: string): HTMLDivElement|null {
2020-06-09 23:13:26 +02:00
const element = document.getElementById(`div-${userId}`);
if(!element){
return null;
}
2020-06-09 23:13:26 +02:00
const connnectingSpinnerDiv = element.getElementsByClassName('connecting-spinner').item(0) as HTMLDivElement|null;
return connnectingSpinnerDiv;
}
private getColorByString(str: String) : String|null {
let hash = 0;
if (str.length === 0) return null;
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
hash = hash & hash;
}
let color = '#';
for (let i = 0; i < 3; i++) {
2020-06-09 23:13:26 +02:00
const value = (hash >> (i * 8)) & 255;
color += ('00' + value.toString(16)).substr(-2);
}
return color;
}
2020-06-03 11:55:31 +02:00
public addNewParticipant(userId: number|string, name: string|undefined, img?: string, showReportUserCallBack?: ShowReportCallBack){
discussionManager.addParticipant(userId, name, img, false, showReportUserCallBack);
2020-10-25 21:59:14 +01:00
}
public removeParticipant(userId: number|string){
discussionManager.removeParticipant(userId);
2020-10-25 21:59:14 +01:00
}
2020-11-17 18:03:44 +01:00
public addTriggerCloseJitsiFrameButton(id: String, Function: Function){
this.triggerCloseJistiFrame.set(id, Function);
}
public removeTriggerCloseJitsiFrameButton(id: String){
this.triggerCloseJistiFrame.delete(id);
}
private triggerCloseJitsiFrameButton(): void {
for (const callback of this.triggerCloseJistiFrame.values()) {
callback();
}
}
2020-11-04 12:42:33 +01:00
/**
* For some reasons, the microphone muted icon or the stream is not always up to date.
* Here, every 30 seconds, we are "reseting" the streams and sending again the constraints to the other peers via the data channel again (see SimplePeer::pushVideoToRemoteUser)
**/
2020-10-24 14:40:51 +02:00
private pingCameraStatus(){
/*setInterval(() => {
2020-10-24 14:40:51 +02:00
console.log('ping camera status');
this.triggerUpdatedLocalStreamCallbacks(this.localStream);
}, 30000);*/
2020-10-24 14:40:51 +02:00
}
2020-10-25 21:59:14 +01:00
public addNewMessage(name: string, message: string, isMe: boolean = false){
discussionManager.addMessage(name, message, isMe);
//when there are new message, show discussion
if(!discussionManager.activatedDiscussion) {
discussionManager.showDiscussionPart();
}
2020-10-25 21:59:14 +01:00
}
2020-10-26 14:13:51 +01:00
public addSendMessageCallback(userId: string|number, callback: SendMessageCallback){
discussionManager.onSendMessageCallback(userId, callback);
2020-10-25 21:59:14 +01:00
}
get activatedDiscussion(){
return discussionManager.activatedDiscussion;
}
2020-11-10 12:38:32 +01:00
public setUserInputManager(userInputManager : UserInputManager){
this.userInputManager = userInputManager;
discussionManager.setUserInputManager(userInputManager);
2020-11-10 12:38:32 +01:00
}
public setShowReportModalCallBacks(callback: ShowReportCallBack){
this.showReportModalCallBacks.add(callback);
}
public setHelpCameraSettingsCallBack(callback: HelpCameraSettingsCallBack){
this.helpCameraSettingsCallBacks.add(callback);
}
private showHelpCameraSettingsCallBack(){
for(const callBack of this.helpCameraSettingsCallBacks){
callBack();
}
}
2021-05-10 20:49:17 +02:00
//FIX ME SOUNDMETER: check stalability of sound meter calculation
/*updateSoudMeter(){
try{
const volume = parseInt(((this.mySoundMeter ? this.mySoundMeter.getVolume() : 0) / 10).toFixed(0));
this.setVolumeSoundMeter(volume, this.mySoundMeterElement);
for(const indexUserId of this.soundMeters.keys()){
const soundMeter = this.soundMeters.get(indexUserId);
const soundMeterElement = this.soundMeterElements.get(indexUserId);
if(!soundMeter || !soundMeterElement){
return;
}
const volumeByUser = parseInt((soundMeter.getVolume() / 10).toFixed(0));
this.setVolumeSoundMeter(volumeByUser, soundMeterElement);
}
}catch(err){
//console.error(err);
}
2021-05-10 20:49:17 +02:00
}*/
private setVolumeSoundMeter(volume: number, element: HTMLDivElement){
if(volume <= 0 && !element.classList.contains('active')){
return;
}
element.classList.remove('active');
if(volume <= 0){
return;
}
element.classList.add('active');
element.childNodes.forEach((value: ChildNode, index) => {
const elementChildre = element.children.item(index);
if(!elementChildre){
return;
}
elementChildre.classList.remove('active');
if((index +1) > volume){
return;
}
elementChildre.classList.add('active');
});
}
public getNotification(){
//Get notification
if (!DISABLE_NOTIFICATIONS && window.Notification && Notification.permission !== "granted") {
Notification.requestPermission().catch((err) => {
console.error(`Notification permission error`, err);
});
}
}
public createNotification(userName: string){
if(this.focused){
return;
}
if (window.Notification && Notification.permission === "granted") {
const title = 'WorkAdventure';
const options = {
body: `Hi! ${userName} wants to discuss with you, don't be afraid!`,
icon: '/resources/logos/logo-WA-min.png',
image: '/resources/logos/logo-WA-min.png',
badge: '/resources/logos/logo-WA-min.png',
};
new Notification(title, options);
//new Notification(`Hi! ${userName} wants to discuss with you, don't be afraid!`);
}
}
2020-06-03 11:55:31 +02:00
}
export const mediaManager = new MediaManager();