Merge branch 'develop' into action-button
# Conflicts: # front/dist/resources/style/style.css # front/src/Phaser/Game/GameScene.ts
This commit is contained in:
commit
3aaeda6f80
34 changed files with 940 additions and 324 deletions
|
@ -16,31 +16,35 @@ class CoWebsiteManager {
|
|||
private opened: iframeStates = iframeStates.closed;
|
||||
|
||||
private observers = new Array<CoWebsiteStateChangedCallback>();
|
||||
/**
|
||||
* Quickly going in and out of an iframe trigger can create conflicts between the iframe states.
|
||||
* So we use this promise to queue up every cowebsite state transition
|
||||
*/
|
||||
private currentOperationPromise: Promise<void> = Promise.resolve();
|
||||
private cowebsiteDiv: HTMLDivElement;
|
||||
|
||||
private close(): HTMLDivElement {
|
||||
const cowebsiteDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteDivId);
|
||||
cowebsiteDiv.classList.remove('loaded'); //edit the css class to trigger the transition
|
||||
cowebsiteDiv.classList.add('hidden');
|
||||
constructor() {
|
||||
this.cowebsiteDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteDivId);
|
||||
}
|
||||
|
||||
private close(): void {
|
||||
this.cowebsiteDiv.classList.remove('loaded'); //edit the css class to trigger the transition
|
||||
this.cowebsiteDiv.classList.add('hidden');
|
||||
this.opened = iframeStates.closed;
|
||||
return cowebsiteDiv;
|
||||
}
|
||||
private load(): HTMLDivElement {
|
||||
const cowebsiteDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteDivId);
|
||||
cowebsiteDiv.classList.remove('hidden'); //edit the css class to trigger the transition
|
||||
cowebsiteDiv.classList.add('loading');
|
||||
private load(): void {
|
||||
this.cowebsiteDiv.classList.remove('hidden'); //edit the css class to trigger the transition
|
||||
this.cowebsiteDiv.classList.add('loading');
|
||||
this.opened = iframeStates.loading;
|
||||
return cowebsiteDiv;
|
||||
}
|
||||
private open(): HTMLDivElement {
|
||||
const cowebsiteDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteDivId);
|
||||
cowebsiteDiv.classList.remove('loading', 'hidden'); //edit the css class to trigger the transition
|
||||
private open(): void {
|
||||
this.cowebsiteDiv.classList.remove('loading', 'hidden'); //edit the css class to trigger the transition
|
||||
this.opened = iframeStates.opened;
|
||||
return cowebsiteDiv;
|
||||
}
|
||||
|
||||
public loadCoWebsite(url: string): void {
|
||||
const cowebsiteDiv = this.load();
|
||||
cowebsiteDiv.innerHTML = '';
|
||||
this.load();
|
||||
this.cowebsiteDiv.innerHTML = '';
|
||||
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.id = 'cowebsite-iframe';
|
||||
|
@ -48,11 +52,11 @@ class CoWebsiteManager {
|
|||
const onloadPromise = new Promise((resolve) => {
|
||||
iframe.onload = () => resolve();
|
||||
});
|
||||
cowebsiteDiv.appendChild(iframe);
|
||||
this.cowebsiteDiv.appendChild(iframe);
|
||||
const onTimeoutPromise = new Promise((resolve) => {
|
||||
setTimeout(() => resolve(), 2000);
|
||||
});
|
||||
Promise.race([onloadPromise, onTimeoutPromise]).then(() => {
|
||||
this.currentOperationPromise = this.currentOperationPromise.then(() =>Promise.race([onloadPromise, onTimeoutPromise])).then(() => {
|
||||
this.open();
|
||||
setTimeout(() => {
|
||||
this.fire();
|
||||
|
@ -64,24 +68,26 @@ class CoWebsiteManager {
|
|||
* Just like loadCoWebsite but the div can be filled by the user.
|
||||
*/
|
||||
public insertCoWebsite(callback: (cowebsite: HTMLDivElement) => Promise<void>): void {
|
||||
const cowebsiteDiv = this.load();
|
||||
callback(cowebsiteDiv).then(() => {
|
||||
this.load();
|
||||
this.currentOperationPromise = this.currentOperationPromise.then(() => callback(this.cowebsiteDiv)).then(() => {
|
||||
this.open();
|
||||
setTimeout(() => {
|
||||
this.fire();
|
||||
}, animationTime)
|
||||
}, animationTime);
|
||||
}).catch(() => this.closeCoWebsite());
|
||||
}
|
||||
|
||||
public closeCoWebsite(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const cowebsiteDiv = this.close();
|
||||
this.currentOperationPromise = this.currentOperationPromise.then(() => new Promise((resolve, reject) => {
|
||||
if(this.opened === iframeStates.closed) resolve(); //this method may be called twice, in case of iframe error for example
|
||||
this.close();
|
||||
this.fire();
|
||||
setTimeout(() => {
|
||||
this.cowebsiteDiv.innerHTML = '';
|
||||
resolve();
|
||||
setTimeout(() => cowebsiteDiv.innerHTML = '', 500)
|
||||
}, animationTime)
|
||||
});
|
||||
}));
|
||||
return this.currentOperationPromise;
|
||||
}
|
||||
|
||||
public getGameSize(): {width: number, height: number} {
|
||||
|
@ -104,6 +110,7 @@ class CoWebsiteManager {
|
|||
}
|
||||
}
|
||||
|
||||
//todo: is it still useful to allow any kind of observers?
|
||||
public onStateChange(observer: CoWebsiteStateChangedCallback) {
|
||||
this.observers.push(observer);
|
||||
}
|
||||
|
|
231
front/src/WebRtc/DiscussionManager.ts
Normal file
231
front/src/WebRtc/DiscussionManager.ts
Normal file
|
@ -0,0 +1,231 @@
|
|||
import {HtmlUtils} from "./HtmlUtils";
|
||||
import {MediaManager, ReportCallback, UpdatedLocalStreamCallback} from "./MediaManager";
|
||||
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||
export type SendMessageCallback = (message:string) => void;
|
||||
|
||||
export class DiscussionManager {
|
||||
private mainContainer: HTMLDivElement;
|
||||
|
||||
private divDiscuss?: HTMLDivElement;
|
||||
private divParticipants?: HTMLDivElement;
|
||||
private nbpParticipants?: HTMLParagraphElement;
|
||||
private divMessages?: HTMLParagraphElement;
|
||||
private buttonActiveDiscussion?: HTMLButtonElement;
|
||||
|
||||
private participants: Map<number|string, HTMLDivElement> = new Map<number|string, HTMLDivElement>();
|
||||
|
||||
private activeDiscussion: boolean = false;
|
||||
|
||||
private sendMessageCallBack : Map<number|string, SendMessageCallback> = new Map<number|string, SendMessageCallback>();
|
||||
|
||||
private userInputManager?: UserInputManager;
|
||||
|
||||
constructor(private mediaManager: MediaManager, name: string) {
|
||||
this.mainContainer = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
|
||||
this.createDiscussPart(name);
|
||||
}
|
||||
|
||||
private createDiscussPart(name: string) {
|
||||
this.divDiscuss = document.createElement('div');
|
||||
this.divDiscuss.classList.add('discussion');
|
||||
|
||||
const buttonCloseDiscussion: HTMLButtonElement = document.createElement('button');
|
||||
this.buttonActiveDiscussion = document.createElement('button');
|
||||
buttonCloseDiscussion.classList.add('close-btn');
|
||||
buttonCloseDiscussion.innerHTML = `<img src="resources/logos/close.svg"/>`;
|
||||
buttonCloseDiscussion.addEventListener('click', () => {
|
||||
this.hideDiscussion();
|
||||
this.showButtonDiscussionBtn();
|
||||
});
|
||||
this.buttonActiveDiscussion.classList.add('active-btn');
|
||||
this.buttonActiveDiscussion.innerHTML = `<img src="resources/logos/discussion.svg"/>`;
|
||||
this.buttonActiveDiscussion.addEventListener('click', () => {
|
||||
this.showDiscussionPart();
|
||||
});
|
||||
this.divDiscuss.appendChild(buttonCloseDiscussion);
|
||||
this.divDiscuss.appendChild(this.buttonActiveDiscussion);
|
||||
|
||||
const myName: HTMLParagraphElement = document.createElement('p');
|
||||
myName.innerText = name.toUpperCase();
|
||||
this.nbpParticipants = document.createElement('p');
|
||||
this.nbpParticipants.innerText = 'PARTICIPANTS (1)';
|
||||
|
||||
this.divParticipants = document.createElement('div');
|
||||
this.divParticipants.classList.add('participants');
|
||||
|
||||
this.divMessages = document.createElement('div');
|
||||
this.divMessages.classList.add('messages');
|
||||
this.divMessages.innerHTML = "<h2>Local messages</h2>"
|
||||
|
||||
this.divDiscuss.appendChild(myName);
|
||||
this.divDiscuss.appendChild(this.nbpParticipants);
|
||||
this.divDiscuss.appendChild(this.divParticipants);
|
||||
this.divDiscuss.appendChild(this.divMessages);
|
||||
|
||||
const sendDivMessage: HTMLDivElement = document.createElement('div');
|
||||
sendDivMessage.classList.add('send-message');
|
||||
const inputMessage: HTMLInputElement = document.createElement('input');
|
||||
inputMessage.type = "text";
|
||||
inputMessage.addEventListener('keyup', (event: KeyboardEvent) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
if(inputMessage.value === null
|
||||
|| inputMessage.value === ''
|
||||
|| inputMessage.value === undefined) {
|
||||
return;
|
||||
}
|
||||
this.addMessage(name, inputMessage.value, true);
|
||||
for(const callback of this.sendMessageCallBack.values()) {
|
||||
callback(inputMessage.value);
|
||||
}
|
||||
inputMessage.value = "";
|
||||
}
|
||||
});
|
||||
sendDivMessage.appendChild(inputMessage);
|
||||
this.divDiscuss.appendChild(sendDivMessage);
|
||||
|
||||
//append in main container
|
||||
this.mainContainer.appendChild(this.divDiscuss);
|
||||
|
||||
this.addParticipant('me', 'Moi', undefined, true);
|
||||
}
|
||||
|
||||
public addParticipant(
|
||||
userId: number|string,
|
||||
name: string|undefined,
|
||||
img?: string|undefined,
|
||||
isMe: boolean = false,
|
||||
reportCallback?: ReportCallback
|
||||
) {
|
||||
const divParticipant: HTMLDivElement = document.createElement('div');
|
||||
divParticipant.classList.add('participant');
|
||||
divParticipant.id = `participant-${userId}`;
|
||||
|
||||
const divImgParticipant: HTMLImageElement = document.createElement('img');
|
||||
divImgParticipant.src = 'resources/logos/boy.svg';
|
||||
if (img !== undefined) {
|
||||
divImgParticipant.src = img;
|
||||
}
|
||||
const divPParticipant: HTMLParagraphElement = document.createElement('p');
|
||||
if(!name){
|
||||
name = 'Anonymous';
|
||||
}
|
||||
divPParticipant.innerText = name;
|
||||
|
||||
divParticipant.appendChild(divImgParticipant);
|
||||
divParticipant.appendChild(divPParticipant);
|
||||
|
||||
if(!isMe) {
|
||||
const reportBanUserAction: HTMLButtonElement = document.createElement('button');
|
||||
reportBanUserAction.classList.add('report-btn')
|
||||
reportBanUserAction.innerText = 'Report';
|
||||
reportBanUserAction.addEventListener('click', () => {
|
||||
if(reportCallback) {
|
||||
this.mediaManager.showReportModal(`${userId}`, name ?? '', reportCallback);
|
||||
}else{
|
||||
console.info('report feature is not activated!');
|
||||
}
|
||||
});
|
||||
divParticipant.appendChild(reportBanUserAction);
|
||||
}
|
||||
|
||||
this.divParticipants?.appendChild(divParticipant);
|
||||
|
||||
this.participants.set(userId, divParticipant);
|
||||
this.showButtonDiscussionBtn();
|
||||
|
||||
this.updateParticipant(this.participants.size);
|
||||
}
|
||||
|
||||
public updateParticipant(nb: number) {
|
||||
if (!this.nbpParticipants) {
|
||||
return;
|
||||
}
|
||||
this.nbpParticipants.innerText = `PARTICIPANTS (${nb})`;
|
||||
}
|
||||
|
||||
public addMessage(name: string, message: string, isMe: boolean = false) {
|
||||
const divMessage: HTMLDivElement = document.createElement('div');
|
||||
divMessage.classList.add('message');
|
||||
if(isMe){
|
||||
divMessage.classList.add('me');
|
||||
}
|
||||
|
||||
const pMessage: HTMLParagraphElement = document.createElement('p');
|
||||
const date = new Date();
|
||||
if(isMe){
|
||||
name = 'Moi';
|
||||
}
|
||||
pMessage.innerHTML = `<span style="font-weight: bold">${name}</span>
|
||||
<span style="color:#bac2cc;display:inline-block;font-size:12px;">
|
||||
${date.getHours()}:${date.getMinutes()}
|
||||
</span>`;
|
||||
divMessage.appendChild(pMessage);
|
||||
|
||||
const userMessage: HTMLParagraphElement = document.createElement('p');
|
||||
userMessage.innerText = message;
|
||||
userMessage.classList.add('body');
|
||||
divMessage.appendChild(userMessage);
|
||||
|
||||
this.divMessages?.appendChild(divMessage);
|
||||
}
|
||||
|
||||
public removeParticipant(userId: number|string){
|
||||
const element = this.participants.get(userId);
|
||||
if(element){
|
||||
element.remove();
|
||||
this.participants.delete(userId);
|
||||
}
|
||||
//if all participant leave, hide discussion button
|
||||
if(this.participants.size === 1){
|
||||
this.hideButtonDiscussionBtn();
|
||||
}
|
||||
|
||||
this.sendMessageCallBack.delete(userId);
|
||||
}
|
||||
|
||||
public onSendMessageCallback(userId: string|number, callback: SendMessageCallback): void {
|
||||
this.sendMessageCallBack.set(userId, callback);
|
||||
}
|
||||
|
||||
get activatedDiscussion(){
|
||||
return this.activeDiscussion;
|
||||
}
|
||||
|
||||
private showButtonDiscussionBtn(){
|
||||
//if it's first participant, show discussion button
|
||||
if(this.activatedDiscussion || this.participants.size === 1) {
|
||||
return;
|
||||
}
|
||||
this.buttonActiveDiscussion?.classList.add('active');
|
||||
}
|
||||
|
||||
private showDiscussion(){
|
||||
this.activeDiscussion = true;
|
||||
if(this.userInputManager) {
|
||||
this.userInputManager.clearAllInputKeyboard();
|
||||
}
|
||||
this.divDiscuss?.classList.add('active');
|
||||
}
|
||||
|
||||
private hideDiscussion(){
|
||||
this.activeDiscussion = false;
|
||||
if(this.userInputManager) {
|
||||
this.userInputManager.initKeyBoardEvent();
|
||||
}
|
||||
this.divDiscuss?.classList.remove('active');
|
||||
}
|
||||
|
||||
private hideButtonDiscussionBtn(){
|
||||
this.buttonActiveDiscussion?.classList.remove('active');
|
||||
}
|
||||
|
||||
public setUserInputManager(userInputManager : UserInputManager){
|
||||
this.userInputManager = userInputManager;
|
||||
}
|
||||
|
||||
public showDiscussionPart(){
|
||||
this.showDiscussion();
|
||||
this.hideButtonDiscussionBtn();
|
||||
}
|
||||
}
|
|
@ -50,8 +50,9 @@ class JitsiFactory {
|
|||
delete options.jwt;
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
options.onload = () => resolve(); //we want for the iframe to be loaded before triggering animations.
|
||||
setTimeout(() => resolve(), 2000); //failsafe in case the iframe is deleted before loading or too long to load
|
||||
this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options);
|
||||
this.jitsiApi.executeCommand('displayName', playerName);
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import {DivImportance, layoutManager} from "./LayoutManager";
|
||||
import {HtmlUtils} from "./HtmlUtils";
|
||||
import {DiscussionManager, SendMessageCallback} from "./DiscussionManager";
|
||||
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
const videoConstraint: boolean|MediaTrackConstraints = {
|
||||
|
@ -38,57 +40,65 @@ export class MediaManager {
|
|||
private cinemaBtn: HTMLDivElement;
|
||||
private monitorBtn: HTMLDivElement;
|
||||
|
||||
private discussionManager: DiscussionManager;
|
||||
|
||||
private userInputManager?: UserInputManager;
|
||||
|
||||
private hasCamera = true;
|
||||
|
||||
constructor() {
|
||||
|
||||
this.myCamVideo = this.getElementByIdOrFail<HTMLVideoElement>('myCamVideo');
|
||||
this.webrtcInAudio = this.getElementByIdOrFail<HTMLAudioElement>('audio-webrtc-in');
|
||||
this.myCamVideo = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideo');
|
||||
this.webrtcInAudio = HtmlUtils.getElementByIdOrFail<HTMLAudioElement>('audio-webrtc-in');
|
||||
this.webrtcInAudio.volume = 0.2;
|
||||
|
||||
this.microphoneBtn = this.getElementByIdOrFail<HTMLDivElement>('btn-micro');
|
||||
this.microphoneClose = this.getElementByIdOrFail<HTMLImageElement>('microphone-close');
|
||||
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();
|
||||
this.enableMicrophone();
|
||||
//update tracking
|
||||
});
|
||||
this.microphone = this.getElementByIdOrFail<HTMLImageElement>('microphone');
|
||||
this.microphone = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('microphone');
|
||||
this.microphone.addEventListener('click', (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
this.disableMicrophone();
|
||||
//update tracking
|
||||
});
|
||||
|
||||
this.cinemaBtn = this.getElementByIdOrFail<HTMLDivElement>('btn-video');
|
||||
this.cinemaClose = this.getElementByIdOrFail<HTMLImageElement>('cinema-close');
|
||||
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();
|
||||
this.enableCamera();
|
||||
//update tracking
|
||||
});
|
||||
this.cinema = this.getElementByIdOrFail<HTMLImageElement>('cinema');
|
||||
this.cinema = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('cinema');
|
||||
this.cinema.addEventListener('click', (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
this.disableCamera();
|
||||
//update tracking
|
||||
});
|
||||
|
||||
this.monitorBtn = this.getElementByIdOrFail<HTMLDivElement>('btn-monitor');
|
||||
this.monitorClose = this.getElementByIdOrFail<HTMLImageElement>('monitor-close');
|
||||
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();
|
||||
//update tracking
|
||||
});
|
||||
this.monitor = this.getElementByIdOrFail<HTMLImageElement>('monitor');
|
||||
this.monitor = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('monitor');
|
||||
this.monitor.style.display = "none";
|
||||
this.monitor.addEventListener('click', (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
this.disableScreenSharing();
|
||||
//update tracking
|
||||
});
|
||||
|
||||
this.discussionManager = new DiscussionManager(this,'');
|
||||
}
|
||||
|
||||
public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void {
|
||||
|
@ -126,16 +136,19 @@ export class MediaManager {
|
|||
}
|
||||
|
||||
public showGameOverlay(){
|
||||
const gameOverlay = this.getElementByIdOrFail('game-overlay');
|
||||
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
|
||||
gameOverlay.classList.add('active');
|
||||
}
|
||||
|
||||
public hideGameOverlay(){
|
||||
const gameOverlay = this.getElementByIdOrFail('game-overlay');
|
||||
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
|
||||
gameOverlay.classList.remove('active');
|
||||
}
|
||||
|
||||
public enableCamera() {
|
||||
if(!this.hasCamera){
|
||||
return;
|
||||
}
|
||||
this.cinemaClose.style.display = "none";
|
||||
this.cinemaBtn.classList.remove("disabled");
|
||||
this.cinema.style.display = "block";
|
||||
|
@ -146,13 +159,7 @@ export class MediaManager {
|
|||
}
|
||||
|
||||
public async disableCamera() {
|
||||
this.cinemaClose.style.display = "block";
|
||||
this.cinema.style.display = "none";
|
||||
this.cinemaBtn.classList.add("disabled");
|
||||
this.constraintsMedia.video = false;
|
||||
this.myCamVideo.srcObject = null;
|
||||
this.stopCamera();
|
||||
|
||||
this.disabledCameraView();
|
||||
if (this.constraintsMedia.audio !== false) {
|
||||
const stream = await this.getCamera();
|
||||
this.triggerUpdatedLocalStreamCallbacks(stream);
|
||||
|
@ -161,6 +168,15 @@ export class MediaManager {
|
|||
}
|
||||
}
|
||||
|
||||
private disabledCameraView(){
|
||||
this.cinemaClose.style.display = "block";
|
||||
this.cinema.style.display = "none";
|
||||
this.cinemaBtn.classList.add("disabled");
|
||||
this.constraintsMedia.video = false;
|
||||
this.myCamVideo.srcObject = null;
|
||||
this.stopCamera();
|
||||
}
|
||||
|
||||
public enableMicrophone() {
|
||||
this.microphoneClose.style.display = "none";
|
||||
this.microphone.style.display = "block";
|
||||
|
@ -267,24 +283,33 @@ export class MediaManager {
|
|||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia(this.constraintsMedia);
|
||||
return this.getLocalStream().catch(() => {
|
||||
console.info('Error get camera, trying with video option at null');
|
||||
this.disabledCameraView();
|
||||
return this.getLocalStream().then((stream : MediaStream) => {
|
||||
this.hasCamera = false;
|
||||
return stream;
|
||||
}).catch((err) => {
|
||||
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;
|
||||
|
||||
return stream;
|
||||
|
||||
//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*/
|
||||
} catch (err) {
|
||||
console.info("error get media ", this.constraintsMedia.video, this.constraintsMedia.audio, err);
|
||||
this.localStream = null;
|
||||
}).catch((err: Error) => {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -355,14 +380,17 @@ export class MediaManager {
|
|||
layoutManager.add(DivImportance.Normal, userId, html);
|
||||
|
||||
if (reportCallBack) {
|
||||
const reportBtn = this.getElementByIdOrFail<HTMLDivElement>(`report-${userId}`);
|
||||
const reportBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(`report-${userId}`);
|
||||
reportBtn.addEventListener('click', (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
this.showReportModal(userId, userName, reportCallBack);
|
||||
});
|
||||
}
|
||||
|
||||
this.remoteVideo.set(userId, this.getElementByIdOrFail<HTMLVideoElement>(userId));
|
||||
this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail<HTMLVideoElement>(userId));
|
||||
|
||||
//permit to create participant in discussion part
|
||||
this.addNewParticipant(userId, userName, undefined, reportCallBack);
|
||||
}
|
||||
|
||||
addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important){
|
||||
|
@ -376,7 +404,7 @@ export class MediaManager {
|
|||
|
||||
layoutManager.add(divImportance, userId, html);
|
||||
|
||||
this.remoteVideo.set(userId, this.getElementByIdOrFail<HTMLVideoElement>(userId));
|
||||
this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail<HTMLVideoElement>(userId));
|
||||
}
|
||||
|
||||
disabledMicrophoneByUserId(userId: number){
|
||||
|
@ -437,6 +465,9 @@ export class MediaManager {
|
|||
removeActiveVideo(userId: string){
|
||||
layoutManager.remove(userId);
|
||||
this.remoteVideo.delete(userId);
|
||||
|
||||
//permit to remove user in discussion part
|
||||
this.removeParticipant(userId);
|
||||
}
|
||||
removeActiveScreenSharingVideo(userId: string) {
|
||||
this.removeActiveVideo(`screen-sharing-${userId}`)
|
||||
|
@ -499,18 +530,9 @@ export class MediaManager {
|
|||
return color;
|
||||
}
|
||||
|
||||
private getElementByIdOrFail<T extends HTMLElement>(id: string): T {
|
||||
const elem = document.getElementById(id);
|
||||
if (elem === null) {
|
||||
throw new Error("Cannot find HTML element with id '"+id+"'");
|
||||
}
|
||||
// FIXME: does not check the type of the returned type
|
||||
return elem as T;
|
||||
}
|
||||
|
||||
private showReportModal(userId: string, userName: string, reportCallBack: ReportCallback){
|
||||
public showReportModal(userId: string, userName: string, reportCallBack: ReportCallback){
|
||||
//create report text area
|
||||
const mainContainer = this.getElementByIdOrFail<HTMLDivElement>('main-container');
|
||||
const mainContainer = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
|
||||
|
||||
const divReport = document.createElement('div');
|
||||
divReport.classList.add('modal-report-user');
|
||||
|
@ -565,7 +587,34 @@ export class MediaManager {
|
|||
mainContainer.appendChild(divReport);
|
||||
}
|
||||
|
||||
public addNewParticipant(userId: number|string, name: string|undefined, img?: string, reportCallBack?: ReportCallback){
|
||||
this.discussionManager.addParticipant(userId, name, img, false, reportCallBack);
|
||||
}
|
||||
|
||||
public removeParticipant(userId: number|string){
|
||||
this.discussionManager.removeParticipant(userId);
|
||||
}
|
||||
|
||||
public addNewMessage(name: string, message: string, isMe: boolean = false){
|
||||
this.discussionManager.addMessage(name, message, isMe);
|
||||
|
||||
//when there are new message, show discussion
|
||||
if(!this.discussionManager.activatedDiscussion) {
|
||||
this.discussionManager.showDiscussionPart();
|
||||
}
|
||||
}
|
||||
|
||||
public addSendMessageCallback(userId: string|number, callback: SendMessageCallback){
|
||||
this.discussionManager.onSendMessageCallback(userId, callback);
|
||||
}
|
||||
|
||||
get activatedDiscussion(){
|
||||
return this.discussionManager.activatedDiscussion;
|
||||
}
|
||||
|
||||
public setUserInputManager(userInputManager : UserInputManager){
|
||||
this.discussionManager.setUserInputManager(userInputManager);
|
||||
}
|
||||
}
|
||||
|
||||
export const mediaManager = new MediaManager();
|
||||
|
|
|
@ -2,6 +2,7 @@ import * as SimplePeerNamespace from "simple-peer";
|
|||
import {mediaManager} from "./MediaManager";
|
||||
import {TURN_SERVER, TURN_USER, TURN_PASSWORD} from "../Enum/EnvironmentVariable";
|
||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import {MESSAGE_TYPE_CONSTRAINT} from "./VideoPeer";
|
||||
|
||||
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
|
||||
|
||||
|
@ -148,6 +149,6 @@ export class ScreenSharingPeer extends Peer {
|
|||
|
||||
public stopPushingScreenSharingToRemoteUser(stream: MediaStream) {
|
||||
this.removeStream(stream);
|
||||
this.write(new Buffer(JSON.stringify({streamEnded: true})));
|
||||
this.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, streamEnded: true})));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
UpdatedLocalStreamCallback
|
||||
} from "./MediaManager";
|
||||
import {ScreenSharingPeer} from "./ScreenSharingPeer";
|
||||
import {VideoPeer} from "./VideoPeer";
|
||||
import {MESSAGE_TYPE_CONSTRAINT, MESSAGE_TYPE_MESSAGE, VideoPeer} from "./VideoPeer";
|
||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||
|
||||
export interface UserSimplePeerInterface{
|
||||
|
@ -38,7 +38,7 @@ export class SimplePeer {
|
|||
private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback;
|
||||
private readonly peerConnectionListeners: Array<PeerConnectionListener> = new Array<PeerConnectionListener>();
|
||||
|
||||
constructor(private Connection: RoomConnection, private enableReporting: boolean) {
|
||||
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);
|
||||
|
@ -145,6 +145,12 @@ export class SimplePeer {
|
|||
mediaManager.addActiveVideo("" + user.userId, reportCallback, name);
|
||||
|
||||
const peer = new VideoPeer(user.userId, user.initiator ? user.initiator : false, this.Connection);
|
||||
|
||||
//permit to send message
|
||||
mediaManager.addSendMessageCallback(user.userId,(message: string) => {
|
||||
peer.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_MESSAGE, name: this.myName.toUpperCase(), message: message})));
|
||||
});
|
||||
|
||||
peer.toClose = false;
|
||||
// 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!
|
||||
|
@ -318,7 +324,7 @@ export class SimplePeer {
|
|||
throw new Error('While adding media, cannot find user with ID ' + userId);
|
||||
}
|
||||
const localStream: MediaStream | null = mediaManager.localStream;
|
||||
PeerConnection.write(new Buffer(JSON.stringify(mediaManager.constraintsMedia)));
|
||||
PeerConnection.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...mediaManager.constraintsMedia})));
|
||||
|
||||
if(!localStream){
|
||||
return;
|
||||
|
|
|
@ -5,6 +5,8 @@ import {RoomConnection} from "../Connexion/RoomConnection";
|
|||
|
||||
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
|
||||
|
||||
export const MESSAGE_TYPE_CONSTRAINT = 'constraint';
|
||||
export const MESSAGE_TYPE_MESSAGE = 'message';
|
||||
/**
|
||||
* A peer connection used to transmit video / audio signals between 2 peers.
|
||||
*/
|
||||
|
@ -78,19 +80,27 @@ export class VideoPeer extends Peer {
|
|||
});
|
||||
|
||||
this.on('data', (chunk: Buffer) => {
|
||||
const constraint = JSON.parse(chunk.toString('utf8'));
|
||||
console.log("data", constraint);
|
||||
if (constraint.audio) {
|
||||
mediaManager.enabledMicrophoneByUserId(this.userId);
|
||||
} else {
|
||||
mediaManager.disabledMicrophoneByUserId(this.userId);
|
||||
const message = JSON.parse(chunk.toString('utf8'));
|
||||
console.log("data", message);
|
||||
|
||||
if(message.type === MESSAGE_TYPE_CONSTRAINT) {
|
||||
const constraint = message;
|
||||
if (constraint.audio) {
|
||||
mediaManager.enabledMicrophoneByUserId(this.userId);
|
||||
} else {
|
||||
mediaManager.disabledMicrophoneByUserId(this.userId);
|
||||
}
|
||||
|
||||
if (constraint.video || constraint.screen) {
|
||||
mediaManager.enabledVideoByUserId(this.userId);
|
||||
} else {
|
||||
this.stream(undefined);
|
||||
mediaManager.disabledVideoByUserId(this.userId);
|
||||
}
|
||||
}
|
||||
|
||||
if (constraint.video || constraint.screen) {
|
||||
mediaManager.enabledVideoByUserId(this.userId);
|
||||
} else {
|
||||
this.stream(undefined);
|
||||
mediaManager.disabledVideoByUserId(this.userId);
|
||||
if(message.type === 'message') {
|
||||
mediaManager.addNewMessage(message.name, message.message);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -163,7 +173,7 @@ export class VideoPeer extends Peer {
|
|||
private pushVideoToRemoteUser() {
|
||||
try {
|
||||
const localStream: MediaStream | null = mediaManager.localStream;
|
||||
this.write(new Buffer(JSON.stringify(mediaManager.constraintsMedia)));
|
||||
this.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...mediaManager.constraintsMedia})));
|
||||
|
||||
if(!localStream){
|
||||
return;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue