Merge branch 'develop' of github.com:thecodingmachine/workadventure into metadataScriptingApi
This commit is contained in:
commit
ca3f5c599a
160 changed files with 4066 additions and 3583 deletions
|
@ -1,6 +1,7 @@
|
|||
import {HtmlUtils} from "./HtmlUtils";
|
||||
import {Subject} from "rxjs";
|
||||
import {iframeListener} from "../Api/IframeListener";
|
||||
import {touchScreenManager} from "../Touch/TouchScreenManager";
|
||||
|
||||
enum iframeStates {
|
||||
closed = 1,
|
||||
|
@ -11,12 +12,17 @@ enum iframeStates {
|
|||
const cowebsiteDivId = 'cowebsite'; // the id of the whole container.
|
||||
const cowebsiteMainDomId = 'cowebsite-main'; // the id of the parent div of the iframe.
|
||||
const cowebsiteAsideDomId = 'cowebsite-aside'; // the id of the parent div of the iframe.
|
||||
const cowebsiteCloseButtonId = 'cowebsite-close';
|
||||
export const cowebsiteCloseButtonId = 'cowebsite-close';
|
||||
const cowebsiteFullScreenButtonId = 'cowebsite-fullscreen';
|
||||
const cowebsiteOpenFullScreenImageId = 'cowebsite-fullscreen-open';
|
||||
const cowebsiteCloseFullScreenImageId = 'cowebsite-fullscreen-close';
|
||||
const animationTime = 500; //time used by the css transitions, in ms.
|
||||
|
||||
interface TouchMoveCoordinates {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
class CoWebsiteManager {
|
||||
|
||||
private opened: iframeStates = iframeStates.closed;
|
||||
|
@ -32,6 +38,7 @@ class CoWebsiteManager {
|
|||
private resizing: boolean = false;
|
||||
private cowebsiteMainDom: HTMLDivElement;
|
||||
private cowebsiteAsideDom: HTMLDivElement;
|
||||
private previousTouchMoveCoordinates: TouchMoveCoordinates|null = null; //only use on touchscreens to track touch movement
|
||||
|
||||
get width(): number {
|
||||
return this.cowebsiteDiv.clientWidth;
|
||||
|
@ -62,32 +69,61 @@ class CoWebsiteManager {
|
|||
this.cowebsiteMainDom = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteMainDomId);
|
||||
this.cowebsiteAsideDom = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteAsideDomId);
|
||||
|
||||
this.initResizeListeners();
|
||||
if (touchScreenManager.supportTouchScreen) {
|
||||
this.initResizeListeners(true);
|
||||
}
|
||||
this.initResizeListeners(false);
|
||||
|
||||
HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId).addEventListener('click', () => {
|
||||
const buttonCloseFrame = HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId);
|
||||
buttonCloseFrame.addEventListener('click', () => {
|
||||
buttonCloseFrame.blur();
|
||||
this.closeCoWebsite();
|
||||
});
|
||||
HtmlUtils.getElementByIdOrFail(cowebsiteFullScreenButtonId).addEventListener('click', () => {
|
||||
|
||||
const buttonFullScreenFrame = HtmlUtils.getElementByIdOrFail(cowebsiteFullScreenButtonId);
|
||||
buttonFullScreenFrame.addEventListener('click', () => {
|
||||
buttonFullScreenFrame.blur();
|
||||
this.fullscreen();
|
||||
});
|
||||
}
|
||||
|
||||
private initResizeListeners() {
|
||||
const movecallback = (event:MouseEvent) => {
|
||||
this.verticalMode ? this.height += event.movementY / this.getDevicePixelRatio() : this.width -= event.movementX / this.getDevicePixelRatio();
|
||||
private initResizeListeners(touchMode:boolean) {
|
||||
const movecallback = (event:MouseEvent|TouchEvent) => {
|
||||
let x, y;
|
||||
if (event.type === 'mousemove') {
|
||||
x = (event as MouseEvent).movementX / this.getDevicePixelRatio();
|
||||
y = (event as MouseEvent).movementY / this.getDevicePixelRatio();
|
||||
} else {
|
||||
const touchEvent = (event as TouchEvent).touches[0];
|
||||
const last = {x: touchEvent.pageX, y: touchEvent.pageY};
|
||||
const previous = this.previousTouchMoveCoordinates as TouchMoveCoordinates;
|
||||
this.previousTouchMoveCoordinates = last;
|
||||
x = last.x - previous.x;
|
||||
y = last.y - previous.y;
|
||||
}
|
||||
|
||||
|
||||
this.verticalMode ? this.height += y : this.width -= x;
|
||||
this.fire();
|
||||
}
|
||||
|
||||
this.cowebsiteAsideDom.addEventListener('mousedown', (event) => {
|
||||
this.cowebsiteAsideDom.addEventListener( touchMode ? 'touchstart' : 'mousedown', (event) => {
|
||||
this.resizing = true;
|
||||
this.getIframeDom().style.display = 'none';
|
||||
if (touchMode) {
|
||||
const touchEvent = (event as TouchEvent).touches[0];
|
||||
this.previousTouchMoveCoordinates = {x: touchEvent.pageX, y: touchEvent.pageY};
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', movecallback);
|
||||
document.addEventListener(touchMode ? 'touchmove' : 'mousemove', movecallback);
|
||||
});
|
||||
|
||||
document.addEventListener('mouseup', (event) => {
|
||||
document.addEventListener(touchMode ? 'touchend' : 'mouseup', (event) => {
|
||||
if (!this.resizing) return;
|
||||
document.removeEventListener('mousemove', movecallback);
|
||||
if (touchMode) {
|
||||
this.previousTouchMoveCoordinates = null;
|
||||
}
|
||||
document.removeEventListener(touchMode ? 'touchmove' : 'mousemove', movecallback);
|
||||
this.getIframeDom().style.display = 'block';
|
||||
this.resizing = false;
|
||||
});
|
||||
|
@ -152,7 +188,10 @@ class CoWebsiteManager {
|
|||
setTimeout(() => {
|
||||
this.fire();
|
||||
}, animationTime)
|
||||
}).catch(() => this.closeCoWebsite());
|
||||
}).catch((err) => {
|
||||
console.error('Error loadCoWebsite => ', err);
|
||||
this.closeCoWebsite()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -166,7 +205,10 @@ class CoWebsiteManager {
|
|||
setTimeout(() => {
|
||||
this.fire();
|
||||
}, animationTime);
|
||||
}).catch(() => this.closeCoWebsite());
|
||||
}).catch((err) => {
|
||||
console.error('Error insertCoWebsite => ', err);
|
||||
this.closeCoWebsite();
|
||||
});
|
||||
}
|
||||
|
||||
public closeCoWebsite(): Promise<void> {
|
||||
|
|
12
front/src/WebRtc/DeviceUtils.ts
Normal file
12
front/src/WebRtc/DeviceUtils.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
export function isIOS(): boolean {
|
||||
return [
|
||||
'iPad Simulator',
|
||||
'iPhone Simulator',
|
||||
'iPod Simulator',
|
||||
'iPad',
|
||||
'iPhone',
|
||||
'iPod'
|
||||
].includes(navigator.platform)
|
||||
// iPad on iOS 13 detection
|
||||
|| (navigator.userAgent.includes("Mac") && "ontouchend" in document)
|
||||
}
|
|
@ -171,6 +171,8 @@ export class DiscussionManager {
|
|||
const date = new Date();
|
||||
if(isMe){
|
||||
name = 'Me';
|
||||
} else {
|
||||
name = HtmlUtils.escapeHtml(name);
|
||||
}
|
||||
pMessage.innerHTML = `<span style="font-weight: bold">${name}</span>
|
||||
<span style="color:#bac2cc;display:inline-block;font-size:12px;">
|
||||
|
|
|
@ -35,7 +35,12 @@ export class HtmlUtils {
|
|||
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
||||
text = HtmlUtils.escapeHtml(text);
|
||||
return text.replace(urlRegex, (url: string) => {
|
||||
return '<a href="' + url + '" target="_blank" style=":visited {color: white}">' + url + '</a>';
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.target = "_blank";
|
||||
const text = document.createTextNode(url);
|
||||
link.appendChild(text);
|
||||
return link.outerHTML;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -8,32 +8,11 @@ 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
|
||||
|
||||
const 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
|
||||
};
|
||||
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
|
||||
};
|
||||
import {helpCameraSettingsVisibleStore} from "../Stores/HelpCameraSettingsStore";
|
||||
|
||||
export type UpdatedLocalStreamCallback = (media: MediaStream | null) => void;
|
||||
export type StartScreenSharingCallback = (media: MediaStream) => void;
|
||||
|
@ -42,31 +21,17 @@ export type ReportCallback = (message: string) => void;
|
|||
export type ShowReportCallBack = (userId: string, userName: string | undefined) => void;
|
||||
export type HelpCameraSettingsCallBack = () => void;
|
||||
|
||||
import {cowebsiteCloseButtonId} from "./CoWebsiteManager";
|
||||
|
||||
export class MediaManager {
|
||||
localStream: MediaStream | null = null;
|
||||
localScreenCapture: MediaStream | null = null;
|
||||
private remoteVideo: Map<string, HTMLVideoElement> = new Map<string, HTMLVideoElement>();
|
||||
myCamVideo: HTMLVideoElement;
|
||||
cinemaClose: HTMLImageElement;
|
||||
cinema: HTMLImageElement;
|
||||
monitorClose: HTMLImageElement;
|
||||
monitor: HTMLImageElement;
|
||||
microphoneClose: HTMLImageElement;
|
||||
microphone: HTMLImageElement;
|
||||
webrtcInAudio: HTMLAudioElement;
|
||||
//FIX ME SOUNDMETER: check stalability of sound meter calculation
|
||||
//mySoundMeterElement: HTMLDivElement;
|
||||
private webrtcOutAudio: HTMLAudioElement;
|
||||
|
||||
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 focused: boolean = true;
|
||||
|
@ -82,54 +47,6 @@ export class MediaManager {
|
|||
|
||||
constructor() {
|
||||
|
||||
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;
|
||||
|
||||
this.microphoneBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-micro');
|
||||
this.microphoneClose = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('microphone-close');
|
||||
this.microphoneClose.style.display = "none";
|
||||
this.microphoneClose.addEventListener('click', (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
requestedMicrophoneState.enableMicrophone();
|
||||
});
|
||||
this.microphone = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('microphone');
|
||||
this.microphone.addEventListener('click', (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
requestedMicrophoneState.disableMicrophone();
|
||||
});
|
||||
|
||||
this.cinemaBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-video');
|
||||
this.cinemaClose = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('cinema-close');
|
||||
this.cinemaClose.style.display = "none";
|
||||
this.cinemaClose.addEventListener('click', (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
requestedCameraState.enableWebcam();
|
||||
});
|
||||
this.cinema = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('cinema');
|
||||
this.cinema.addEventListener('click', (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
requestedCameraState.disableWebcam();
|
||||
});
|
||||
|
||||
this.monitorBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-monitor');
|
||||
this.monitorClose = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('monitor-close');
|
||||
this.monitorClose.style.display = "block";
|
||||
this.monitorClose.addEventListener('click', (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
//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();
|
||||
requestedScreenSharingState.disableScreenSharing();
|
||||
});
|
||||
|
||||
this.pingCameraStatus();
|
||||
|
||||
//FIX ME SOUNDMETER: check stability of sound meter calculation
|
||||
|
@ -144,87 +61,43 @@ export class MediaManager {
|
|||
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();
|
||||
layoutManager.addInformation('warning', 'Camera access denied. Click here and check your browser permissions.', () => {
|
||||
helpCameraSettingsVisibleStore.set(true);
|
||||
}, 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;
|
||||
let isScreenSharing = false;
|
||||
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();
|
||||
layoutManager.addInformation('warning', 'Screen sharing denied. Click here and check your browser permissions.', () => {
|
||||
helpCameraSettingsVisibleStore.set(true);
|
||||
}, 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;
|
||||
|
||||
isScreenSharing = true;
|
||||
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;
|
||||
if (isScreenSharing) {
|
||||
isScreenSharing = false;
|
||||
this.removeActiveScreenSharingVideo('me');
|
||||
}
|
||||
|
||||
//screenSharingStream = null;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
screenSharingAvailableStore.subscribe((available) => {
|
||||
/*screenSharingAvailableStore.subscribe((available) => {
|
||||
if (available) {
|
||||
document.querySelector('.btn-monitor')?.classList.remove('hide');
|
||||
} else {
|
||||
document.querySelector('.btn-monitor')?.classList.add('hide');
|
||||
}
|
||||
});
|
||||
});*/
|
||||
}
|
||||
|
||||
public updateScene(){
|
||||
|
@ -232,49 +105,18 @@ export class MediaManager {
|
|||
//this.updateSoudMeter();
|
||||
}
|
||||
|
||||
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 {
|
||||
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
|
||||
gameOverlay.classList.add('active');
|
||||
|
||||
const buttonCloseFrame = HtmlUtils.getElementByIdOrFail('cowebsite-close');
|
||||
const buttonCloseFrame = HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId);
|
||||
const functionTrigger = () => {
|
||||
this.triggerCloseJitsiFrameButton();
|
||||
}
|
||||
buttonCloseFrame.removeEventListener('click', functionTrigger);
|
||||
buttonCloseFrame.removeEventListener('click', () => {
|
||||
buttonCloseFrame.blur();
|
||||
functionTrigger();
|
||||
});
|
||||
|
||||
gameOverlayVisibilityStore.showGameOverlay();
|
||||
}
|
||||
|
@ -283,53 +125,22 @@ export class MediaManager {
|
|||
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
|
||||
gameOverlay.classList.remove('active');
|
||||
|
||||
const buttonCloseFrame = HtmlUtils.getElementByIdOrFail('cowebsite-close');
|
||||
const buttonCloseFrame = HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId);
|
||||
const functionTrigger = () => {
|
||||
this.triggerCloseJitsiFrameButton();
|
||||
}
|
||||
buttonCloseFrame.addEventListener('click', functionTrigger);
|
||||
buttonCloseFrame.addEventListener('click', () => {
|
||||
buttonCloseFrame.blur();
|
||||
functionTrigger();
|
||||
});
|
||||
|
||||
gameOverlayVisibilityStore.hideGameOverlay();
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
private enableScreenSharingStyle(){
|
||||
this.monitorClose.style.display = "none";
|
||||
this.monitor.style.display = "block";
|
||||
this.monitorBtn.classList.add("enabled");
|
||||
}
|
||||
|
||||
private disableScreenSharingStyle(){
|
||||
this.monitorClose.style.display = "block";
|
||||
this.monitor.style.display = "none";
|
||||
this.monitorBtn.classList.remove("enabled");
|
||||
}
|
||||
|
||||
addActiveVideo(user: UserSimplePeerInterface, userName: string = "") {
|
||||
this.webrtcInAudio.play();
|
||||
|
||||
const userId = '' + user.userId
|
||||
|
||||
userName = userName.toUpperCase();
|
||||
|
@ -345,7 +156,7 @@ export class MediaManager {
|
|||
<img title="report this user" src="resources/logos/report.svg">
|
||||
<span>Report/Block</span>
|
||||
</button>
|
||||
<video id="${userId}" autoplay></video>
|
||||
<video id="${userId}" autoplay playsinline></video>
|
||||
<img src="resources/logos/blockSign.svg" id="blocking-${userId}" class="block-logo">
|
||||
<div id="soundMeter-${userId}" class="sound-progress">
|
||||
<span></span>
|
||||
|
@ -382,7 +193,7 @@ export class MediaManager {
|
|||
userId = this.getScreenSharingId(userId);
|
||||
const html = `
|
||||
<div id="div-${userId}" class="video-container">
|
||||
<video id="${userId}" autoplay></video>
|
||||
<video id="${userId}" autoplay playsinline></video>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
@ -477,10 +288,6 @@ export class MediaManager {
|
|||
this.removeActiveVideo(this.getScreenSharingId(userId))
|
||||
}
|
||||
|
||||
playWebrtcOutSound(): void {
|
||||
this.webrtcOutAudio.play();
|
||||
}
|
||||
|
||||
isConnecting(userId: string): void {
|
||||
const connectingSpinnerDiv = this.getSpinner(userId);
|
||||
if (connectingSpinnerDiv === null) {
|
||||
|
@ -595,16 +402,6 @@ export class MediaManager {
|
|||
this.showReportModalCallBacks.add(callback);
|
||||
}
|
||||
|
||||
public setHelpCameraSettingsCallBack(callback: HelpCameraSettingsCallBack) {
|
||||
this.helpCameraSettingsCallBacks.add(callback);
|
||||
}
|
||||
|
||||
private showHelpCameraSettingsCallBack() {
|
||||
for (const callBack of this.helpCameraSettingsCallBacks) {
|
||||
callBack();
|
||||
}
|
||||
}
|
||||
|
||||
//FIX ME SOUNDMETER: check stalability of sound meter calculation
|
||||
/*updateSoudMeter(){
|
||||
try{
|
||||
|
@ -650,12 +447,32 @@ export class MediaManager {
|
|||
public getNotification(){
|
||||
//Get notification
|
||||
if (!DISABLE_NOTIFICATIONS && window.Notification && Notification.permission !== "granted") {
|
||||
Notification.requestPermission().catch((err) => {
|
||||
console.error(`Notification permission error`, err);
|
||||
});
|
||||
if (this.checkNotificationPromise()) {
|
||||
Notification.requestPermission().catch((err) => {
|
||||
console.error(`Notification permission error`, err);
|
||||
});
|
||||
} else {
|
||||
Notification.requestPermission();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the browser supports the modern version of the Notification API (which is Promise based) or false
|
||||
* if we are on Safari...
|
||||
*
|
||||
* See https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API
|
||||
*/
|
||||
private checkNotificationPromise(): boolean {
|
||||
try {
|
||||
Notification.requestPermission().then();
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public createNotification(userName: string){
|
||||
if(this.focused){
|
||||
return;
|
||||
|
|
|
@ -19,7 +19,7 @@ export class ScreenSharingPeer extends Peer {
|
|||
public _connected: boolean = false;
|
||||
private userId: number;
|
||||
|
||||
constructor(user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) {
|
||||
constructor(user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection, stream: MediaStream | null) {
|
||||
super({
|
||||
initiator: initiator ? initiator : false,
|
||||
//reconnectTimer: 10000,
|
||||
|
@ -60,6 +60,7 @@ export class ScreenSharingPeer extends Peer {
|
|||
const message = JSON.parse(chunk.toString('utf8'));
|
||||
if (message.streamEnded !== true) {
|
||||
console.error('Unexpected message on screen sharing peer connection');
|
||||
return;
|
||||
}
|
||||
mediaManager.removeActiveScreenSharingVideo("" + this.userId);
|
||||
});
|
||||
|
@ -81,7 +82,9 @@ export class ScreenSharingPeer extends Peer {
|
|||
this._onFinish();
|
||||
});
|
||||
|
||||
this.pushScreenSharingToRemoteUser();
|
||||
if (stream) {
|
||||
this.addStream(stream);
|
||||
}
|
||||
}
|
||||
|
||||
private sendWebrtcScreenSharingSignal(data: unknown) {
|
||||
|
@ -141,16 +144,6 @@ export class ScreenSharingPeer extends Peer {
|
|||
}
|
||||
}
|
||||
|
||||
private pushScreenSharingToRemoteUser() {
|
||||
const localScreenCapture: MediaStream | null = mediaManager.localScreenCapture;
|
||||
if(!localScreenCapture){
|
||||
return;
|
||||
}
|
||||
|
||||
this.addStream(localScreenCapture);
|
||||
return;
|
||||
}
|
||||
|
||||
public stopPushingScreenSharingToRemoteUser(stream: MediaStream) {
|
||||
this.removeStream(stream);
|
||||
this.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, streamEnded: true})));
|
||||
|
|
|
@ -15,7 +15,10 @@ import {connectionManager} from "../Connexion/ConnectionManager";
|
|||
import {GameConnexionTypes} from "../Url/UrlManager";
|
||||
import {blackListManager} from "./BlackListManager";
|
||||
import {get} from "svelte/store";
|
||||
import {localStreamStore, obtainedMediaConstraintStore} from "../Stores/MediaStore";
|
||||
import {localStreamStore, LocalStreamStoreValue, obtainedMediaConstraintStore} from "../Stores/MediaStore";
|
||||
import {screenSharingLocalStreamStore} from "../Stores/ScreenSharingStore";
|
||||
import {DivImportance, layoutManager} from "./LayoutManager";
|
||||
import {HtmlUtils} from "./HtmlUtils";
|
||||
|
||||
export interface UserSimplePeerInterface{
|
||||
userId: number;
|
||||
|
@ -39,9 +42,9 @@ export class SimplePeer {
|
|||
|
||||
private PeerScreenSharingConnectionArray: Map<number, ScreenSharingPeer> = new Map<number, ScreenSharingPeer>();
|
||||
private PeerConnectionArray: Map<number, VideoPeer> = new Map<number, VideoPeer>();
|
||||
private readonly sendLocalVideoStreamCallback: UpdatedLocalStreamCallback;
|
||||
private readonly sendLocalScreenSharingStreamCallback: StartScreenSharingCallback;
|
||||
private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback;
|
||||
private readonly unsubscribers: (() => void)[] = [];
|
||||
private readonly peerConnectionListeners: Array<PeerConnectionListener> = new Array<PeerConnectionListener>();
|
||||
private readonly userId: number;
|
||||
private lastWebrtcUserName: string|undefined;
|
||||
|
@ -49,13 +52,32 @@ export class SimplePeer {
|
|||
|
||||
constructor(private Connection: RoomConnection, private enableReporting: boolean, private myName: string) {
|
||||
// We need to go through this weird bound function pointer in order to be able to "free" this reference later.
|
||||
this.sendLocalVideoStreamCallback = this.sendLocalVideoStream.bind(this);
|
||||
this.sendLocalScreenSharingStreamCallback = this.sendLocalScreenSharingStream.bind(this);
|
||||
this.stopLocalScreenSharingStreamCallback = this.stopLocalScreenSharingStream.bind(this);
|
||||
|
||||
mediaManager.onUpdateLocalStream(this.sendLocalVideoStreamCallback);
|
||||
mediaManager.onStartScreenSharing(this.sendLocalScreenSharingStreamCallback);
|
||||
mediaManager.onStopScreenSharing(this.stopLocalScreenSharingStreamCallback);
|
||||
this.unsubscribers.push(localStreamStore.subscribe((streamResult) => {
|
||||
this.sendLocalVideoStream(streamResult);
|
||||
}));
|
||||
|
||||
let localScreenCapture: MediaStream|null = null;
|
||||
|
||||
this.unsubscribers.push(screenSharingLocalStreamStore.subscribe((streamResult) => {
|
||||
if (streamResult.type === 'error') {
|
||||
// Let's ignore screen sharing errors, we will deal with those in a different way.
|
||||
return;
|
||||
}
|
||||
|
||||
if (streamResult.stream !== null) {
|
||||
localScreenCapture = streamResult.stream;
|
||||
this.sendLocalScreenSharingStream(localScreenCapture);
|
||||
} else {
|
||||
if (localScreenCapture) {
|
||||
this.stopLocalScreenSharingStream(localScreenCapture);
|
||||
localScreenCapture = null;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this.userId = Connection.getUserId();
|
||||
this.initialise();
|
||||
}
|
||||
|
@ -106,13 +128,19 @@ export class SimplePeer {
|
|||
if(!user.initiator){
|
||||
return;
|
||||
}
|
||||
this.createPeerConnection(user);
|
||||
const streamResult = get(localStreamStore);
|
||||
let stream : MediaStream | null = null;
|
||||
if (streamResult.type === 'success' && streamResult.stream) {
|
||||
stream = streamResult.stream;
|
||||
}
|
||||
|
||||
this.createPeerConnection(user, stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* create peer connection to bind users
|
||||
*/
|
||||
private createPeerConnection(user : UserSimplePeerInterface) : VideoPeer | null {
|
||||
private createPeerConnection(user : UserSimplePeerInterface, localStream: MediaStream | null) : VideoPeer | null {
|
||||
const peerConnection = this.PeerConnectionArray.get(user.userId)
|
||||
if (peerConnection) {
|
||||
if (peerConnection.destroyed) {
|
||||
|
@ -122,11 +150,11 @@ export class SimplePeer {
|
|||
if (!peerConnexionDeleted) {
|
||||
throw 'Error to delete peer connection';
|
||||
}
|
||||
this.createPeerConnection(user);
|
||||
//return this.createPeerConnection(user, localStream);
|
||||
} else {
|
||||
peerConnection.toClose = false;
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
let name = user.name;
|
||||
|
@ -144,7 +172,7 @@ export class SimplePeer {
|
|||
this.lastWebrtcUserName = user.webRtcUser;
|
||||
this.lastWebrtcPassword = user.webRtcPassword;
|
||||
|
||||
const peer = new VideoPeer(user, user.initiator ? user.initiator : false, this.Connection);
|
||||
const peer = new VideoPeer(user, user.initiator ? user.initiator : false, this.Connection, localStream);
|
||||
|
||||
//permit to send message
|
||||
mediaManager.addSendMessageCallback(user.userId,(message: string) => {
|
||||
|
@ -155,8 +183,9 @@ export class SimplePeer {
|
|||
// When a connection is established to a video stream, and if a screen sharing is taking place,
|
||||
// the user sharing screen should also initiate a connection to the remote user!
|
||||
peer.on('connect', () => {
|
||||
if (mediaManager.localScreenCapture) {
|
||||
this.sendLocalScreenSharingStreamToUser(user.userId);
|
||||
const streamResult = get(screenSharingLocalStreamStore);
|
||||
if (streamResult.type === 'success' && streamResult.stream !== null) {
|
||||
this.sendLocalScreenSharingStreamToUser(user.userId, streamResult.stream);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -175,7 +204,7 @@ export class SimplePeer {
|
|||
/**
|
||||
* create peer connection to bind users
|
||||
*/
|
||||
private createPeerScreenSharingConnection(user : UserSimplePeerInterface) : ScreenSharingPeer | null{
|
||||
private createPeerScreenSharingConnection(user : UserSimplePeerInterface, stream: MediaStream | null) : ScreenSharingPeer | null{
|
||||
const peerConnection = this.PeerScreenSharingConnectionArray.get(user.userId);
|
||||
if(peerConnection){
|
||||
if(peerConnection.destroyed){
|
||||
|
@ -185,7 +214,7 @@ export class SimplePeer {
|
|||
if(!peerConnexionDeleted){
|
||||
throw 'Error to delete peer connection';
|
||||
}
|
||||
this.createPeerConnection(user);
|
||||
this.createPeerConnection(user, stream);
|
||||
}else {
|
||||
peerConnection.toClose = false;
|
||||
}
|
||||
|
@ -204,7 +233,7 @@ export class SimplePeer {
|
|||
user.webRtcPassword = this.lastWebrtcPassword;
|
||||
}
|
||||
|
||||
const peer = new ScreenSharingPeer(user, user.initiator ? user.initiator : false, this.Connection);
|
||||
const peer = new ScreenSharingPeer(user, user.initiator ? user.initiator : false, this.Connection, stream);
|
||||
this.PeerScreenSharingConnectionArray.set(user.userId, peer);
|
||||
|
||||
for (const peerConnectionListener of this.peerConnectionListeners) {
|
||||
|
@ -217,7 +246,6 @@ export class SimplePeer {
|
|||
* This is triggered twice. Once by the server, and once by a remote client disconnecting
|
||||
*/
|
||||
private closeConnection(userId : number) {
|
||||
mediaManager.playWebrtcOutSound();
|
||||
try {
|
||||
const peer = this.PeerConnectionArray.get(userId);
|
||||
if (peer === undefined) {
|
||||
|
@ -234,7 +262,7 @@ export class SimplePeer {
|
|||
|
||||
const userIndex = this.Users.findIndex(user => user.userId === userId);
|
||||
if(userIndex < 0){
|
||||
throw 'Couln\'t delete user';
|
||||
throw 'Couldn\'t delete user';
|
||||
} else {
|
||||
this.Users.splice(userIndex, 1);
|
||||
}
|
||||
|
@ -294,7 +322,9 @@ export class SimplePeer {
|
|||
* Unregisters any held event handler.
|
||||
*/
|
||||
public unregister() {
|
||||
mediaManager.removeUpdateLocalStreamEventListener(this.sendLocalVideoStreamCallback);
|
||||
for (const unsubscriber of this.unsubscribers) {
|
||||
unsubscriber();
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
@ -302,7 +332,13 @@ export class SimplePeer {
|
|||
try {
|
||||
//if offer type, create peer connection
|
||||
if(data.signal.type === "offer"){
|
||||
this.createPeerConnection(data);
|
||||
const streamResult = get(localStreamStore);
|
||||
let stream : MediaStream | null = null;
|
||||
if (streamResult.type === 'success' && streamResult.stream) {
|
||||
stream = streamResult.stream;
|
||||
}
|
||||
|
||||
this.createPeerConnection(data, stream);
|
||||
}
|
||||
const peer = this.PeerConnectionArray.get(data.userId);
|
||||
if (peer !== undefined) {
|
||||
|
@ -318,18 +354,26 @@ export class SimplePeer {
|
|||
private receiveWebrtcScreenSharingSignal(data: WebRtcSignalReceivedMessageInterface) {
|
||||
if (blackListManager.isBlackListed(data.userId)) return;
|
||||
console.log("receiveWebrtcScreenSharingSignal", data);
|
||||
const streamResult = get(screenSharingLocalStreamStore);
|
||||
let stream : MediaStream | null = null;
|
||||
if (streamResult.type === 'success' && streamResult.stream !== null) {
|
||||
stream = streamResult.stream;
|
||||
}
|
||||
|
||||
try {
|
||||
//if offer type, create peer connection
|
||||
if(data.signal.type === "offer"){
|
||||
this.createPeerScreenSharingConnection(data);
|
||||
this.createPeerScreenSharingConnection(data, stream);
|
||||
}
|
||||
const peer = this.PeerScreenSharingConnectionArray.get(data.userId);
|
||||
if (peer !== undefined) {
|
||||
peer.signal(data.signal);
|
||||
} else {
|
||||
console.error('Could not find peer whose ID is "'+data.userId+'" in receiveWebrtcScreenSharingSignal');
|
||||
console.info('tentative to create new peer connexion');
|
||||
this.sendLocalScreenSharingStreamToUser(data.userId);
|
||||
console.info('Attempt to create new peer connexion');
|
||||
if (stream) {
|
||||
this.sendLocalScreenSharingStreamToUser(data.userId, stream);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`receiveWebrtcSignal => ${data.userId}`, e);
|
||||
|
@ -339,21 +383,19 @@ export class SimplePeer {
|
|||
}
|
||||
}
|
||||
|
||||
private pushVideoToRemoteUser(userId : number) {
|
||||
private pushVideoToRemoteUser(userId: number, streamResult: LocalStreamStoreValue) {
|
||||
try {
|
||||
const PeerConnection = this.PeerConnectionArray.get(userId);
|
||||
if (!PeerConnection) {
|
||||
throw new Error('While adding media, cannot find user with ID ' + userId);
|
||||
}
|
||||
|
||||
const result = get(localStreamStore);
|
||||
PeerConnection.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...streamResult.constraints})));
|
||||
|
||||
PeerConnection.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...result.constraints})));
|
||||
|
||||
if (result.type === 'error') {
|
||||
if (streamResult.type === 'error') {
|
||||
return;
|
||||
}
|
||||
const localStream: MediaStream | null = result.stream;
|
||||
const localStream: MediaStream | null = streamResult.stream;
|
||||
|
||||
if(!localStream){
|
||||
return;
|
||||
|
@ -370,15 +412,11 @@ export class SimplePeer {
|
|||
}
|
||||
}
|
||||
|
||||
private pushScreenSharingToRemoteUser(userId : number) {
|
||||
private pushScreenSharingToRemoteUser(userId: number, localScreenCapture: MediaStream) {
|
||||
const PeerConnection = this.PeerScreenSharingConnectionArray.get(userId);
|
||||
if (!PeerConnection) {
|
||||
throw new Error('While pushing screen sharing, cannot find user with ID ' + userId);
|
||||
}
|
||||
const localScreenCapture: MediaStream | null = mediaManager.localScreenCapture;
|
||||
if(!localScreenCapture){
|
||||
return;
|
||||
}
|
||||
|
||||
for (const track of localScreenCapture.getTracks()) {
|
||||
PeerConnection.addTrack(track, localScreenCapture);
|
||||
|
@ -386,23 +424,18 @@ export class SimplePeer {
|
|||
return;
|
||||
}
|
||||
|
||||
public sendLocalVideoStream(){
|
||||
public sendLocalVideoStream(streamResult: LocalStreamStoreValue){
|
||||
for (const user of this.Users) {
|
||||
this.pushVideoToRemoteUser(user.userId);
|
||||
this.pushVideoToRemoteUser(user.userId, streamResult);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered locally when clicking on the screen sharing button
|
||||
*/
|
||||
public sendLocalScreenSharingStream() {
|
||||
if (!mediaManager.localScreenCapture) {
|
||||
console.error('Could not find localScreenCapture to share')
|
||||
return;
|
||||
}
|
||||
|
||||
public sendLocalScreenSharingStream(localScreenCapture: MediaStream) {
|
||||
for (const user of this.Users) {
|
||||
this.sendLocalScreenSharingStreamToUser(user.userId);
|
||||
this.sendLocalScreenSharingStreamToUser(user.userId, localScreenCapture);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -415,11 +448,11 @@ export class SimplePeer {
|
|||
}
|
||||
}
|
||||
|
||||
private sendLocalScreenSharingStreamToUser(userId: number): void {
|
||||
private sendLocalScreenSharingStreamToUser(userId: number, localScreenCapture: MediaStream): void {
|
||||
if (blackListManager.isBlackListed(userId)) return;
|
||||
// If a connection already exists with user (because it is already sharing a screen with us... let's use this connection)
|
||||
if (this.PeerScreenSharingConnectionArray.has(userId)) {
|
||||
this.pushScreenSharingToRemoteUser(userId);
|
||||
this.pushScreenSharingToRemoteUser(userId, localScreenCapture);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -427,7 +460,7 @@ export class SimplePeer {
|
|||
userId,
|
||||
initiator: true
|
||||
};
|
||||
const PeerConnectionScreenSharing = this.createPeerScreenSharingConnection(screenSharingUser);
|
||||
const PeerConnectionScreenSharing = this.createPeerScreenSharingConnection(screenSharingUser, localScreenCapture);
|
||||
if (!PeerConnectionScreenSharing) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ export class VideoPeer extends Peer {
|
|||
private onBlockSubscribe: Subscription;
|
||||
private onUnBlockSubscribe: Subscription;
|
||||
|
||||
constructor(public user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) {
|
||||
constructor(public user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection, localStream: MediaStream | null) {
|
||||
super({
|
||||
initiator: initiator ? initiator : false,
|
||||
//reconnectTimer: 10000,
|
||||
|
@ -107,7 +107,7 @@ export class VideoPeer extends Peer {
|
|||
this._onFinish();
|
||||
});
|
||||
|
||||
this.pushVideoToRemoteUser();
|
||||
this.pushVideoToRemoteUser(localStream);
|
||||
this.onBlockSubscribe = blackListManager.onBlockStream.subscribe((userId) => {
|
||||
if (userId === this.userId) {
|
||||
this.toggleRemoteStream(false);
|
||||
|
@ -190,9 +190,8 @@ export class VideoPeer extends Peer {
|
|||
}
|
||||
}
|
||||
|
||||
private pushVideoToRemoteUser() {
|
||||
private pushVideoToRemoteUser(localStream: MediaStream | null) {
|
||||
try {
|
||||
const localStream: MediaStream | null = mediaManager.localStream;
|
||||
this.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...get(obtainedMediaConstraintStore)})));
|
||||
|
||||
if(!localStream){
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue