2020-08-13 18:21:48 +02:00
import { DivImportance , layoutManager } from "./LayoutManager" ;
2020-08-21 22:53:17 +02:00
import { HtmlUtils } from "./HtmlUtils" ;
2020-12-04 11:30:35 +01:00
import { discussionManager , SendMessageCallback } from "./DiscussionManager" ;
2020-11-10 12:38:32 +01:00
import { UserInputManager } from "../Phaser/UserInput/UserInputManager" ;
2020-11-27 16:24:07 +01:00
import { VIDEO_QUALITY_SELECT } from "../Administration/ConsoleGlobalMessageManager" ;
2020-10-23 16:16:30 +02:00
declare const navigator :any ; // eslint-disable-line @typescript-eslint/no-explicit-any
2020-08-13 18:21:48 +02:00
2020-11-27 16:24:07 +01:00
const localValueVideo = localStorage . getItem ( VIDEO_QUALITY_SELECT ) ;
let valueVideo = 20 ;
if ( localValueVideo ) {
valueVideo = parseInt ( localValueVideo ) ;
}
let videoConstraint : boolean | MediaTrackConstraints = {
width : { min : 640 , ideal : 1280 , max : 1920 } ,
height : { min : 400 , ideal : 720 } ,
2021-01-10 17:03:05 +01:00
frameRate : { ideal : valueVideo } ,
2020-11-27 16:24:07 +01:00
facingMode : "user" ,
resizeMode : 'crop-and-scale' ,
aspectRatio : 1.777777778
2020-05-03 14:29:45 +02:00
} ;
2021-01-30 02:13:03 +01:00
let audioConstraint : boolean | MediaTrackConstraints = {
//TODO: make these values configurable in the game settings menu and store them in localstorage
autoGainControl : false ,
echoCancellation : true ,
noiseSuppression : false
} ;
2020-06-23 14:56:57 +02:00
2020-08-31 15:21:05 +02:00
export type UpdatedLocalStreamCallback = ( media : MediaStream | null ) = > void ;
export type StartScreenSharingCallback = ( media : MediaStream ) = > void ;
export type StopScreenSharingCallback = ( media : MediaStream ) = > void ;
2020-10-13 19:56:42 +02:00
export type ReportCallback = ( message : string ) = > void ;
2020-06-23 14:56:57 +02:00
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)
// TODO: verify that microphone event listeners are not triggered plenty of time NOW (since MediaManager is created many times!!!!)
export class MediaManager {
2020-06-03 11:55:31 +02:00
localStream : MediaStream | null = null ;
2020-06-06 17:03:10 +02:00
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 ;
2020-06-06 17:03:10 +02:00
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 ;
2020-12-11 15:35:13 +01:00
private webrtcOutAudio : HTMLAudioElement ;
2020-06-10 12:15:25 +02:00
constraintsMedia : MediaStreamConstraints = {
2021-01-30 02:13:03 +01:00
audio : audioConstraint ,
2020-05-03 14:29:45 +02:00
video : videoConstraint
} ;
2020-06-23 14:56:57 +02:00
updatedLocalStreamCallBacks : Set < UpdatedLocalStreamCallback > = new Set < UpdatedLocalStreamCallback > ( ) ;
2020-08-21 22:53:17 +02:00
startScreenSharingCallBacks : Set < StartScreenSharingCallback > = new Set < StartScreenSharingCallback > ( ) ;
stopScreenSharingCallBacks : Set < StopScreenSharingCallback > = new Set < StopScreenSharingCallback > ( ) ;
2020-08-22 15:26:40 +02:00
private microphoneBtn : HTMLDivElement ;
private cinemaBtn : HTMLDivElement ;
private monitorBtn : HTMLDivElement ;
2020-06-08 09:20:36 +02:00
2020-10-24 14:13:23 +02:00
private previousConstraint : MediaStreamConstraints ;
2020-10-26 22:39:52 +01:00
private focused : boolean = true ;
private lastUpdateScene : Date = new Date ( ) ;
private setTimeOutlastUpdateScene? : NodeJS.Timeout ;
2020-10-24 14:13:23 +02:00
2020-11-10 14:03:29 +01:00
private hasCamera = true ;
2020-11-17 18:03:44 +01:00
private triggerCloseJistiFrame : Map < String , Function > = new Map < String , Function > ( ) ;
2020-08-18 00:12:38 +02:00
constructor ( ) {
2020-04-19 19:32:38 +02:00
2020-10-25 19:38:00 +01:00
this . myCamVideo = HtmlUtils . getElementByIdOrFail < HTMLVideoElement > ( 'myCamVideo' ) ;
this . webrtcInAudio = HtmlUtils . getElementByIdOrFail < HTMLAudioElement > ( 'audio-webrtc-in' ) ;
2020-12-11 15:35:13 +01:00
this . webrtcOutAudio = HtmlUtils . getElementByIdOrFail < HTMLAudioElement > ( 'audio-webrtc-out' ) ;
2020-05-14 20:39:30 +02:00
this . webrtcInAudio . volume = 0.2 ;
2020-12-11 15:35:13 +01:00
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 ) = > {
2020-04-19 19:32:38 +02:00
e . preventDefault ( ) ;
2020-08-20 00:05:00 +02:00
this . enableMicrophone ( ) ;
2020-04-19 19:32:38 +02:00
//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 ) = > {
2020-04-19 19:32:38 +02:00
e . preventDefault ( ) ;
2020-08-20 00:05:00 +02:00
this . disableMicrophone ( ) ;
2020-04-19 19:32:38 +02:00
//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 ) = > {
2020-04-19 19:32:38 +02:00
e . preventDefault ( ) ;
2020-08-20 00:05:00 +02:00
this . enableCamera ( ) ;
2020-04-19 19:32:38 +02:00
//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 ) = > {
2020-04-19 19:32:38 +02:00
e . preventDefault ( ) ;
2020-08-20 00:05:00 +02:00
this . disableCamera ( ) ;
2020-04-19 19:32:38 +02:00
//update tracking
} ) ;
2020-06-06 17:03:10 +02:00
2020-10-25 19:38:00 +01:00
this . monitorBtn = HtmlUtils . getElementByIdOrFail < HTMLDivElement > ( 'btn-monitor' ) ;
this . monitorClose = HtmlUtils . getElementByIdOrFail < HTMLImageElement > ( 'monitor-close' ) ;
2020-06-06 17:03:10 +02:00
this . monitorClose . style . display = "block" ;
2020-08-18 14:59:50 +02:00
this . monitorClose . addEventListener ( 'click' , ( e : MouseEvent ) = > {
2020-06-06 17:03:10 +02:00
e . preventDefault ( ) ;
2020-08-20 00:05:00 +02:00
this . enableScreenSharing ( ) ;
2020-06-06 17:03:10 +02:00
//update tracking
} ) ;
2020-10-25 19:38:00 +01:00
this . monitor = HtmlUtils . getElementByIdOrFail < HTMLImageElement > ( 'monitor' ) ;
2020-06-06 17:03:10 +02:00
this . monitor . style . display = "none" ;
2020-08-18 14:59:50 +02:00
this . monitor . addEventListener ( 'click' , ( e : MouseEvent ) = > {
2020-06-06 17:03:10 +02:00
e . preventDefault ( ) ;
2020-08-20 00:05:00 +02:00
this . disableScreenSharing ( ) ;
2020-06-06 17:03:10 +02:00
//update tracking
} ) ;
2020-10-25 21:59:14 +01:00
2020-10-24 14:13:23 +02: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
2020-11-27 14:51:50 +01:00
this . checkActiveUser ( ) ; //todo: desactivated in case of bug
2020-04-26 20:55:20 +02:00
}
2020-04-19 19:32:38 +02:00
2020-10-26 22:39:52 +01:00
public setLastUpdateScene ( ) {
this . lastUpdateScene = new Date ( ) ;
}
public blurCamera() {
if ( ! this . focused ) {
return ;
}
this . focused = false ;
this . previousConstraint = JSON . parse ( JSON . stringify ( this . constraintsMedia ) ) ;
this . disableCamera ( ) ;
}
public focusCamera() {
if ( this . focused ) {
return ;
}
this . focused = true ;
this . applyPreviousConfig ( ) ;
2020-04-26 20:55:20 +02:00
}
2020-04-19 19:32:38 +02:00
2020-08-20 00:05:00 +02:00
public onUpdateLocalStream ( callback : UpdatedLocalStreamCallback ) : void {
2020-06-23 14:56:57 +02:00
this . updatedLocalStreamCallBacks . add ( callback ) ;
}
2020-08-21 22:53:17 +02:00
public onStartScreenSharing ( callback : StartScreenSharingCallback ) : void {
this . startScreenSharingCallBacks . add ( callback ) ;
}
public onStopScreenSharing ( callback : StopScreenSharingCallback ) : void {
this . stopScreenSharingCallBacks . add ( callback ) ;
2020-08-18 00:12:38 +02:00
}
2020-06-23 14:56:57 +02:00
removeUpdateLocalStreamEventListener ( callback : UpdatedLocalStreamCallback ) : void {
this . updatedLocalStreamCallBacks . delete ( callback ) ;
}
2020-08-31 15:21:05 +02:00
private triggerUpdatedLocalStreamCallbacks ( stream : MediaStream | null ) : void {
2020-06-23 14:56:57 +02:00
for ( const callback of this . updatedLocalStreamCallBacks ) {
callback ( stream ) ;
}
}
2020-08-21 22:53:17 +02:00
private triggerStartedScreenSharingCallbacks ( stream : MediaStream ) : void {
for ( const callback of this . startScreenSharingCallBacks ) {
callback ( stream ) ;
}
}
private triggerStoppedScreenSharingCallbacks ( stream : MediaStream ) : void {
for ( const callback of this . stopScreenSharingCallBacks ) {
2020-08-18 00:12:38 +02:00
callback ( stream ) ;
}
}
2020-08-31 14:54:52 +02:00
public showGameOverlay ( ) {
2020-10-25 19:38:00 +01:00
const gameOverlay = HtmlUtils . getElementByIdOrFail ( 'game-overlay' ) ;
2020-08-13 18:21:48 +02:00
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 ) ;
2020-04-19 19:32:38 +02:00
}
2020-09-01 14:43:21 +02:00
public hideGameOverlay ( ) {
2020-10-25 19:38:00 +01:00
const gameOverlay = HtmlUtils . getElementByIdOrFail ( 'game-overlay' ) ;
2020-09-01 14:43:21 +02:00
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 ) ;
2020-09-01 14:43:21 +02:00
}
2020-11-27 16:24:07 +01:00
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 ) ;
} ) ;
}
2020-10-26 15:07:34 +01:00
public enableCamera() {
2020-05-03 14:29:45 +02:00
this . constraintsMedia . video = videoConstraint ;
2021-01-07 10:30:28 +01:00
2020-06-22 22:55:28 +02:00
this . getCamera ( ) . then ( ( stream : MediaStream ) = > {
2021-01-07 11:02:00 +01:00
//TODO show error message tooltip upper of camera button
//TODO message : please check camera permission of your navigator
if ( stream . getVideoTracks ( ) . length === 0 ) {
throw Error ( 'Video track is empty, please check camera permission of your navigator' )
}
2021-01-07 10:30:28 +01:00
this . enableCameraStyle ( ) ;
2020-06-23 14:56:57 +02:00
this . triggerUpdatedLocalStreamCallbacks ( stream ) ;
2021-01-07 11:02:00 +01:00
} ) . catch ( ( err ) = > {
console . error ( err ) ;
this . disableCameraStyle ( ) ;
2020-05-03 17:19:42 +02:00
} ) ;
2020-04-19 19:32:38 +02:00
}
2020-10-26 15:07:34 +01:00
public async disableCamera() {
2020-10-24 14:13:23 +02:00
this . disableCameraStyle ( ) ;
2020-08-31 15:21:05 +02:00
if ( this . constraintsMedia . audio !== false ) {
const stream = await this . getCamera ( ) ;
2020-06-23 14:56:57 +02:00
this . triggerUpdatedLocalStreamCallbacks ( stream ) ;
2020-08-31 15:21:05 +02:00
} else {
this . triggerUpdatedLocalStreamCallbacks ( null ) ;
}
2020-04-19 19:32:38 +02:00
}
2020-10-26 15:07:34 +01:00
public enableMicrophone() {
2020-04-19 19:32:38 +02:00
this . constraintsMedia . audio = true ;
2020-08-31 15:21:05 +02:00
2020-05-03 17:19:42 +02:00
this . getCamera ( ) . then ( ( stream ) = > {
2021-01-07 11:02:00 +01:00
//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' )
}
2021-01-07 10:30:28 +01:00
this . enableMicrophoneStyle ( ) ;
2020-06-23 14:56:57 +02:00
this . triggerUpdatedLocalStreamCallbacks ( stream ) ;
2021-01-07 11:02:00 +01:00
} ) . catch ( ( err ) = > {
console . error ( err ) ;
this . disableMicrophoneStyle ( ) ;
2020-05-03 17:19:42 +02:00
} ) ;
2020-04-19 19:32:38 +02:00
}
2020-10-26 15:07:34 +01:00
public async disableMicrophone() {
2020-10-24 14:13:23 +02:00
this . disableMicrophoneStyle ( ) ;
2020-08-31 14:54:52 +02:00
this . stopMicrophone ( ) ;
2020-08-31 15:21:05 +02:00
if ( this . constraintsMedia . video !== false ) {
const stream = await this . getCamera ( ) ;
2020-06-23 14:56:57 +02:00
this . triggerUpdatedLocalStreamCallbacks ( stream ) ;
2020-08-31 15:21:05 +02:00
} else {
this . triggerUpdatedLocalStreamCallbacks ( null ) ;
}
2020-04-26 20:55:20 +02:00
}
2020-10-24 14:13:23 +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 ;
2020-11-10 15:27:22 +01:00
this . stopCamera ( ) ;
2020-10-24 14:13:23 +02:00
}
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 ;
}
2020-08-20 00:05:00 +02:00
private enableScreenSharing() {
2020-06-06 17:03:10 +02:00
this . monitorClose . style . display = "none" ;
this . monitor . style . display = "block" ;
2020-08-22 15:26:40 +02:00
this . monitorBtn . classList . add ( "enabled" ) ;
2020-06-06 17:03:10 +02:00
this . getScreenMedia ( ) . then ( ( stream ) = > {
2020-08-21 22:53:17 +02:00
this . triggerStartedScreenSharingCallbacks ( stream ) ;
2020-06-06 17:03:10 +02:00
} ) ;
}
2020-08-20 00:05:00 +02:00
private disableScreenSharing() {
2020-06-06 17:03:10 +02:00
this . monitorClose . style . display = "block" ;
this . monitor . style . display = "none" ;
2020-08-22 15:26:40 +02:00
this . monitorBtn . classList . remove ( "enabled" ) ;
2020-08-21 22:53:17 +02:00
this . removeActiveScreenSharingVideo ( 'me' ) ;
2020-06-06 17:03:10 +02:00
this . localScreenCapture ? . getTracks ( ) . forEach ( ( track : MediaStreamTrack ) = > {
track . stop ( ) ;
} ) ;
2020-08-21 22:53:17 +02:00
if ( this . localScreenCapture === null ) {
console . warn ( 'Weird: trying to remove a screen sharing that is not enabled' ) ;
return ;
}
const localScreenCapture = this . localScreenCapture ;
2020-06-06 17:03:10 +02:00
this . getCamera ( ) . then ( ( stream ) = > {
2020-08-21 22:53:17 +02:00
this . triggerStoppedScreenSharingCallbacks ( localScreenCapture ) ;
2021-01-07 09:42:11 +01:00
} ) . catch ( ( err ) = > { //catch error get camera
console . error ( err ) ;
2021-01-06 17:09:17 +01:00
this . triggerStoppedScreenSharingCallbacks ( localScreenCapture ) ;
2020-06-06 17:03:10 +02:00
} ) ;
2020-08-21 22:53:17 +02:00
this . localScreenCapture = null ;
2020-06-06 17:03:10 +02:00
}
//get screen
getScreenMedia ( ) : Promise < MediaStream > {
try {
return this . _startScreenCapture ( )
. then ( ( stream : MediaStream ) = > {
this . localScreenCapture = stream ;
2020-08-20 22:23:38 +02:00
// 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 ( ) ;
} ;
}
2020-08-21 22:53:17 +02:00
this . addScreenSharingActiveVideo ( 'me' , DivImportance . Normal ) ;
HtmlUtils . getElementByIdOrFail < HTMLVideoElement > ( 'screen-sharing-me' ) . srcObject = stream ;
2020-06-06 17:03:10 +02:00
return stream ;
} )
2020-08-18 14:59:50 +02:00
. catch ( ( err : unknown ) = > {
console . error ( "Error => getScreenMedia => " , err ) ;
2020-06-06 17:03:10 +02:00
throw err ;
} ) ;
} catch ( err ) {
return new Promise ( ( resolve , reject ) = > { // eslint-disable-line no-unused-vars
reject ( err ) ;
} ) ;
}
}
private _startScreenCapture() {
2020-10-23 16:16:30 +02:00
if ( navigator . getDisplayMedia ) {
return navigator . getDisplayMedia ( { video : true } ) ;
} else if ( navigator . mediaDevices . getDisplayMedia ) {
return navigator . mediaDevices . getDisplayMedia ( { video : true } ) ;
2020-06-06 17:03:10 +02:00
} else {
return new Promise ( ( resolve , reject ) = > { // eslint-disable-line no-unused-vars
reject ( "error sharing screen" ) ;
} ) ;
}
}
2020-04-19 19:32:38 +02:00
//get camera
2020-06-25 10:43:27 +02:00
async getCamera ( ) : Promise < MediaStream > {
2020-06-22 22:55:28 +02:00
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.' ) ;
}
2020-06-22 22:55:28 +02:00
}
2020-11-10 14:03:29 +01:00
return this . getLocalStream ( ) . catch ( ( ) = > {
console . info ( 'Error get camera, trying with video option at null' ) ;
2020-11-10 15:27:22 +01:00
this . disableCameraStyle ( ) ;
2020-11-10 14:03:29 +01:00
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 ;
} ) ;
} ) ;
2020-06-25 10:43:27 +02:00
2020-11-10 14:03:29 +01:00
//TODO resize remote cam
/ * c o n s o l e . l o g ( t h i s . l o c a l S t r e a m . g e t T r a c k s ( ) ) ;
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 ;
return stream ;
2020-11-10 14:03:29 +01:00
} ) . catch ( ( err : Error ) = > {
2020-06-25 10:43:27 +02:00
throw err ;
2020-11-10 14:03:29 +01:00
} ) ;
2020-04-19 19:32:38 +02:00
}
2020-04-25 20:29:03 +02:00
2020-08-31 14:54:52 +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 ( ) ;
}
}
}
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 ( ) ;
}
2020-10-15 17:58:27 +02:00
addActiveVideo ( userId : string , reportCallBack : ReportCallback | undefined , userName : string = "" ) {
2020-05-14 20:39:30 +02:00
this . webrtcInAudio . play ( ) ;
2020-08-13 18:21:48 +02:00
2020-05-14 20:39:30 +02:00
userName = userName . toUpperCase ( ) ;
2020-06-09 23:13:26 +02:00
const color = this . getColorByString ( userName ) ;
2020-08-13 18:21:48 +02:00
const html = `
< div id = "div-${userId}" class = "video-container" >
< div class = "connecting-spinner" > < / div >
< div class = "rtc-error" style = "display: none" > < / div >
2020-08-16 23:19:04 +02:00
< i id = "name-${userId}" style = "background-color: ${color};" > $ { userName } < / i >
2020-08-13 18:21:48 +02:00
< img id = "microphone-${userId}" src = "resources/logos/microphone-close.svg" >
2020-10-15 17:58:27 +02:00
` +
( ( reportCallBack !== undefined ) ? ` <img id="report- ${ userId } " class="report active" src="resources/logos/report.svg"> ` : '' )
+
` <video id=" ${ userId } " autoplay></video>
2020-08-13 18:21:48 +02:00
< / div >
` ;
layoutManager . add ( DivImportance . Normal , userId , html ) ;
2020-10-15 17:58:27 +02:00
if ( reportCallBack ) {
2020-10-25 19:38:00 +01:00
const reportBtn = HtmlUtils . getElementByIdOrFail < HTMLDivElement > ( ` report- ${ userId } ` ) ;
2020-10-15 17:58:27 +02:00
reportBtn . addEventListener ( 'click' , ( e : MouseEvent ) = > {
e . preventDefault ( ) ;
this . showReportModal ( userId , userName , reportCallBack ) ;
} ) ;
}
2020-10-13 19:56:42 +02:00
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
2020-10-27 20:18:25 +01:00
this . addNewParticipant ( userId , userName , undefined , reportCallBack ) ;
2020-04-26 19:12:01 +02:00
}
2020-10-23 16:16:30 +02:00
2020-09-18 13:57:38 +02:00
addScreenSharingActiveVideo ( userId : string , divImportance : DivImportance = DivImportance . Important ) {
2020-08-20 00:05:00 +02:00
userId = ` screen-sharing- ${ userId } ` ;
const html = `
< div id = "div-${userId}" class = "video-container" >
2020-06-08 09:20:36 +02:00
< video id = "${userId}" autoplay > < / video >
< / div >
2020-08-20 00:05:00 +02:00
` ;
2020-08-21 22:53:17 +02:00
layoutManager . add ( divImportance , userId , html ) ;
2020-08-20 00:05:00 +02:00
2020-10-25 19:38:00 +01:00
this . remoteVideo . set ( userId , HtmlUtils . getElementByIdOrFail < HTMLVideoElement > ( userId ) ) ;
2020-06-08 09:20:36 +02:00
}
2020-10-23 16:16:30 +02:00
2020-09-18 13:57:38 +02:00
disabledMicrophoneByUserId ( userId : number ) {
2020-06-09 23:13:26 +02:00
const element = document . getElementById ( ` microphone- ${ userId } ` ) ;
2020-05-14 20:39:30 +02:00
if ( ! element ) {
return ;
}
2020-11-27 14:51:50 +01:00
element . classList . add ( 'active' ) //todo: why does a method 'disable' add a class 'active'?
2020-05-14 20:39:30 +02:00
}
2020-10-23 16:16:30 +02:00
2020-09-18 13:57:38 +02:00
enabledMicrophoneByUserId ( userId : number ) {
2020-06-09 23:13:26 +02:00
const element = document . getElementById ( ` microphone- ${ userId } ` ) ;
2020-05-14 20:39:30 +02:00
if ( ! element ) {
return ;
}
2020-11-27 14:51:50 +01:00
element . classList . remove ( 'active' ) //todo: why does a method 'enable' remove a class 'active'?
2020-05-14 20:39:30 +02:00
}
2020-10-23 16:16:30 +02:00
2020-09-18 13:57:38 +02:00
disabledVideoByUserId ( userId : number ) {
2020-05-14 20:39:30 +02:00
let element = document . getElementById ( ` ${ userId } ` ) ;
2020-05-14 20:54:34 +02:00
if ( element ) {
element . style . opacity = "0" ;
}
2020-08-16 23:19:04 +02:00
element = document . getElementById ( ` name- ${ userId } ` ) ;
if ( element ) {
element . style . display = "block" ;
2020-05-14 20:39:30 +02:00
}
}
2020-10-23 16:16:30 +02:00
2020-09-18 13:57:38 +02:00
enabledVideoByUserId ( userId : number ) {
2020-05-14 20:39:30 +02:00
let element = document . getElementById ( ` ${ userId } ` ) ;
2020-05-14 20:54:34 +02:00
if ( element ) {
element . style . opacity = "1" ;
}
2020-08-16 23:19:04 +02:00
element = document . getElementById ( ` name- ${ userId } ` ) ;
if ( element ) {
element . style . display = "none" ;
2020-05-14 20:39:30 +02:00
}
}
2020-11-27 14:51:50 +01:00
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 ;
2020-05-02 20:46:02 +02:00
}
2020-09-18 13:57:38 +02:00
addStreamRemoteScreenSharing ( userId : string , stream : MediaStream ) {
2020-08-21 22:53:17 +02:00
// 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 ( ` screen-sharing- ${ userId } ` ) ;
if ( remoteVideo === undefined ) {
this . addScreenSharingActiveVideo ( userId ) ;
}
2020-06-11 23:18:06 +02:00
this . addStreamRemoteVideo ( ` screen-sharing- ${ userId } ` , stream ) ;
}
2020-10-23 16:16:30 +02:00
2020-09-18 13:57:38 +02:00
removeActiveVideo ( userId : string ) {
2020-08-13 18:21:48 +02:00
layoutManager . remove ( userId ) ;
2020-06-10 12:15:25 +02:00
this . remoteVideo . 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
}
2020-09-18 13:57:38 +02:00
removeActiveScreenSharingVideo ( userId : string ) {
2020-06-11 23:18:06 +02:00
this . removeActiveVideo ( ` screen-sharing- ${ userId } ` )
}
2020-12-11 15:35:13 +01:00
playWebrtcOutSound ( ) : void {
this . webrtcOutAudio . play ( ) ;
}
2020-05-14 20:39:30 +02:00
2020-09-18 13:57:38 +02:00
isConnecting ( userId : string ) : void {
2020-06-09 23:13:26 +02:00
const connectingSpinnerDiv = this . getSpinner ( userId ) ;
2020-06-06 22:49:55 +02:00
if ( connectingSpinnerDiv === null ) {
return ;
}
connectingSpinnerDiv . style . display = 'block' ;
}
2020-09-18 13:57:38 +02:00
isConnected ( userId : string ) : void {
2020-06-09 23:13:26 +02:00
const connectingSpinnerDiv = this . getSpinner ( userId ) ;
2020-06-06 22:49:55 +02:00
if ( connectingSpinnerDiv === null ) {
return ;
}
connectingSpinnerDiv . style . display = 'none' ;
}
2020-09-18 13:57:38 +02:00
isError ( userId : string ) : void {
2020-11-27 16:24:07 +01:00
console . info ( "isError" , ` div- ${ userId } ` ) ;
2020-06-09 23:13:26 +02:00
const element = document . getElementById ( ` div- ${ userId } ` ) ;
2020-06-06 22:49:55 +02:00
if ( ! element ) {
return ;
}
2020-06-09 23:13:26 +02:00
const errorDiv = element . getElementsByClassName ( 'rtc-error' ) . item ( 0 ) as HTMLDivElement | null ;
2020-06-06 22:49:55 +02:00
if ( errorDiv === null ) {
return ;
}
errorDiv . style . display = 'block' ;
}
2020-09-18 13:57:38 +02:00
isErrorScreenSharing ( userId : string ) : void {
2020-06-14 14:47:16 +02:00
this . isError ( ` screen-sharing- ${ userId } ` ) ;
}
2020-06-06 22:49:55 +02:00
2020-09-18 13:57:38 +02:00
private getSpinner ( userId : string ) : HTMLDivElement | null {
2020-06-09 23:13:26 +02:00
const element = document . getElementById ( ` div- ${ userId } ` ) ;
2020-06-06 22:49:55 +02:00
if ( ! element ) {
return null ;
}
2020-06-09 23:13:26 +02:00
const connnectingSpinnerDiv = element . getElementsByClassName ( 'connecting-spinner' ) . item ( 0 ) as HTMLDivElement | null ;
2020-06-06 22:49:55 +02:00
return connnectingSpinnerDiv ;
}
2020-10-23 16:16:30 +02:00
2020-05-14 20:39:30 +02:00
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 ;
2020-05-14 20:39:30 +02:00
color += ( '00' + value . toString ( 16 ) ) . substr ( - 2 ) ;
}
return color ;
}
2020-06-03 11:55:31 +02:00
2020-10-27 20:18:25 +01:00
public showReportModal ( userId : string , userName : string , reportCallBack : ReportCallback ) {
2020-10-13 19:56:42 +02:00
//create report text area
2020-10-25 19:38:00 +01:00
const mainContainer = HtmlUtils . getElementByIdOrFail < HTMLDivElement > ( 'main-container' ) ;
2020-10-13 19:56:42 +02:00
const divReport = document . createElement ( 'div' ) ;
divReport . classList . add ( 'modal-report-user' ) ;
const inputHidden = document . createElement ( 'input' ) ;
inputHidden . id = 'input-report-user' ;
inputHidden . type = 'hidden' ;
inputHidden . value = userId ;
divReport . appendChild ( inputHidden ) ;
const titleMessage = document . createElement ( 'p' ) ;
2020-10-13 21:54:08 +02:00
titleMessage . id = 'title-report-user' ;
titleMessage . innerText = 'Open a report' ;
2020-10-13 19:56:42 +02:00
divReport . appendChild ( titleMessage ) ;
2020-10-13 21:54:08 +02:00
const bodyMessage = document . createElement ( 'p' ) ;
bodyMessage . id = 'body-report-user' ;
bodyMessage . innerText = ` You are about to open a report regarding an offensive conduct from user ${ userName . toUpperCase ( ) } . Please explain to us how you think ${ userName . toUpperCase ( ) } breached the code of conduct. ` ;
divReport . appendChild ( bodyMessage ) ;
2020-10-13 19:56:42 +02:00
const imgReportUser = document . createElement ( 'img' ) ;
imgReportUser . id = 'img-report-user' ;
imgReportUser . src = 'resources/logos/report.svg' ;
divReport . appendChild ( imgReportUser ) ;
const textareaUser = document . createElement ( 'textarea' ) ;
textareaUser . id = 'textarea-report-user' ;
2020-10-13 21:54:08 +02:00
textareaUser . placeholder = 'Write ...' ;
2020-10-13 19:56:42 +02:00
divReport . appendChild ( textareaUser ) ;
const buttonReport = document . createElement ( 'button' ) ;
buttonReport . id = 'button-save-report-user' ;
buttonReport . innerText = 'Report' ;
buttonReport . addEventListener ( 'click' , ( ) = > {
if ( ! textareaUser . value ) {
textareaUser . style . border = '1px solid red'
return ;
}
reportCallBack ( textareaUser . value ) ;
divReport . remove ( ) ;
} ) ;
divReport . appendChild ( buttonReport ) ;
const buttonCancel = document . createElement ( 'img' ) ;
buttonCancel . id = 'cancel-report-user' ;
buttonCancel . src = 'resources/logos/close.svg' ;
buttonCancel . addEventListener ( 'click' , ( ) = > {
divReport . remove ( ) ;
} ) ;
divReport . appendChild ( buttonCancel ) ;
mainContainer . appendChild ( divReport ) ;
}
2020-10-27 20:18:25 +01:00
public addNewParticipant ( userId : number | string , name : string | undefined , img? : string , reportCallBack? : ReportCallback ) {
2020-12-04 11:30:35 +01:00
discussionManager . addParticipant ( userId , name , img , false , reportCallBack ) ;
2020-10-25 21:59:14 +01:00
}
public removeParticipant ( userId : number | string ) {
2020-12-04 11:30:35 +01:00
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 ( ) {
2020-11-27 14:51:50 +01:00
/ * s e t I n t e r v a l ( ( ) = > {
2020-10-24 14:40:51 +02:00
console . log ( 'ping camera status' ) ;
2020-11-21 18:47:38 +01:00
this . triggerUpdatedLocalStreamCallbacks ( this . localStream ) ;
2020-11-27 14:51:50 +01:00
} , 30000 ) ; * /
2020-10-24 14:40:51 +02:00
}
2020-10-13 19:56:42 +02:00
2020-10-25 21:59:14 +01:00
public addNewMessage ( name : string , message : string , isMe : boolean = false ) {
2020-12-04 11:30:35 +01:00
discussionManager . addMessage ( name , message , isMe ) ;
2020-11-17 16:46:46 +01:00
//when there are new message, show discussion
2020-12-04 11:30:35 +01:00
if ( ! discussionManager . activatedDiscussion ) {
discussionManager . showDiscussionPart ( ) ;
2020-11-17 16:46:46 +01:00
}
2020-10-25 21:59:14 +01:00
}
2020-10-26 14:13:51 +01:00
public addSendMessageCallback ( userId : string | number , callback : SendMessageCallback ) {
2020-12-04 11:30:35 +01:00
discussionManager . onSendMessageCallback ( userId , callback ) ;
2020-10-25 21:59:14 +01:00
}
2020-10-27 20:46:53 +01:00
get activatedDiscussion ( ) {
2020-12-04 11:30:35 +01:00
return discussionManager . activatedDiscussion ;
2020-10-27 20:46:53 +01:00
}
2020-11-10 12:38:32 +01:00
public setUserInputManager ( userInputManager : UserInputManager ) {
2020-12-04 11:30:35 +01:00
discussionManager . setUserInputManager ( userInputManager ) ;
2020-11-10 12:38:32 +01:00
}
2020-10-26 22:39:52 +01:00
//check if user is active
private checkActiveUser ( ) {
if ( this . setTimeOutlastUpdateScene ) {
clearTimeout ( this . setTimeOutlastUpdateScene ) ;
}
this . setTimeOutlastUpdateScene = setTimeout ( ( ) = > {
const now = new Date ( ) ;
//if last update is more of 10 sec
if ( ( now . getTime ( ) - this . lastUpdateScene . getTime ( ) ) > 10000 ) {
this . blurCamera ( ) ;
} else {
this . focusCamera ( ) ;
}
this . checkActiveUser ( ) ;
} , this . focused ? 10000 : 1000 ) ;
}
2020-06-03 11:55:31 +02:00
}
2020-06-23 14:56:57 +02:00
export const mediaManager = new MediaManager ( ) ;