import { MAP_SIZE } from '@components/Constants' ; import { connection } from '@models/api' ; import { ConnectionStates } from '@models/api/Constants' ; import { t } from '@models/locales' ; import { priorityCalculator } from '@models/middleware/trafficControl' ; import { defaultInformation } from '@models/Participant' ; import { urlParameters } from '@models/url' ; import { addV2 , diffSet , mulV2 } from '@models/utils' ; import { createJitisLocalTracksFromStream } from '@models/utils/jitsiTrack' ; import map from '@stores/Map' ; import participants from '@stores/participants/Participants' ; import { action , autorun , computed , makeObservable , observable , when } from 'mobx' ; export type ErrorType = '' | 'connection' | 'retry' | 'noMic' | 'micPermission' | 'channel' | 'entrance' | 'afk' | 'kicked' ; export class ErrorInfo { @ computed get fatal ( ) { return ! this . type ; } @ observable type : ErrorType = 'entrance' ; @ observable types : Set < ErrorType > = new Set ( ) ; @ observable supressedTypes : Set < ErrorType > = new Set ( ) ; reason ? = '' ; name ? = '' ; @ action setType ( type , name ? , reason ? ) { this . type = type ; this . types . add ( type ) ; this . reason = reason ; this . name = name ; } @ computed get title ( ) { switch ( this . type ) { case 'connection' : return t ( 'etConnection' ) ; case 'retry' : return t ( 'etRetry' ) ; case 'noMic' : return t ( 'etNoMic' ) ; case 'micPermission' : return t ( 'etMicPermission' ) ; case 'channel' : return t ( 'etNoChannel' ) ; case 'entrance' : return '' ; case 'afk' : return t ( 'afkTitle' ) ; case 'kicked' : return `Kicked by ${ this . name }. ${ this . reason }` ; } return this . type ; } @ computed get message ( ) { switch ( this . type ) { case 'connection' : return t ( 'emConnection' ) ; case 'retry' : return t ( 'emRetry' ) ; case 'noMic' : return t ( 'emNoMic' ) ; case 'micPermission' : return t ( 'emMicPermission' ) ; case 'channel' : return t ( 'emNoChannel' ) ; case 'entrance' : return '' ; case 'afk' : return t ( 'afkMessage' ) ; case 'kicked' : return '' ; } return `no message defined for ${ this . type }` ; } show ( ) { if ( this . type === '' ) { const types = diffSet ( this . types , this . supressedTypes ) ; if ( types . size ) { this . type = types . values ( ) . next ( ) . value ; } } return this . type !== '' && ! this . supressedTypes . has ( this . type ) ; } constructor ( ) { makeObservable ( this ) ; if ( urlParameters [ 'testBot' ] !== null ) { this . clear ( ) ; } autorun ( ( ) => { if ( this . show ( ) ) { map . keyInputUsers . add ( 'errorDialog' ) ; } else { map . keyInputUsers . delete ( 'errorDialog' ) ; } } ) ; autorun ( ( ) => { if ( participants . local . physics . awayFromKeyboard ) { this . setType ( 'afk' ) ; } } ) ; } private videoInputs : MediaDeviceInfo [ ] = [ ] ; private audioInputs : MediaDeviceInfo [ ] = [ ] ; private audioOutputs : MediaDeviceInfo [ ] = [ ] ; private enumerateDevices ( ) { navigator . mediaDevices . enumerateDevices ( ) . then ( ( devices ) => { this . videoInputs = [ ] ; this . audioInputs = [ ] ; this . audioOutputs = [ ] ; devices . forEach ( ( device ) => { if ( device . kind === 'videoinput' ) { this . videoInputs . push ( device ) ; } if ( device . kind === 'audioinput' ) { this . audioInputs . push ( device ) ; } if ( device . kind === 'audiooutput' ) { this . audioOutputs . push ( device ) ; } } ) ; } ) . catch ( ( ) => { console . error ( 'Device enumeration error' ) ; } ) ; } @ action connectionStart ( ) { if ( urlParameters . skipEntrance !== null ) { this . clear ( ) ; participants . local . sendInformation ( ) ; } this . enumerateDevices ( ) ; if ( urlParameters . testBot === null ) { when ( ( ) => this . type === '' , ( ) => { setTimeout ( this . checkConnection . bind ( this ) , 4 * 1000 ) ; } ) ; } else { setTimeout ( this . startTestBot . bind ( this ) , 3000 ) ; } } @ action clear ( type ? ) { if ( this . type === 'afk' ) { participants . local . physics . awayFromKeyboard = false ; } if ( type ) { this . types . delete ( type ) ; if ( this . type === type ) { this . type = '' ; } } else { this . types . clear ( ) ; this . type = '' ; } } @ action checkConnection ( ) { if ( connection . state !== ConnectionStates . CONNECTED ) { this . setType ( 'connection' ) ; setTimeout ( this . checkConnection . bind ( this ) , 5 * 1000 ) ; } else { this . clear ( 'connection' ) ; this . checkMic ( ) ; } } @ action checkMic ( ) { if ( participants . localId && ! participants . local . muteAudio && ! connection . conference . getLocalMicTrack ( ) ) { if ( this . audioInputs . length ) { this . setType ( 'micPermission' ) ; } else { this . clear ( 'micPermission' ) ; this . setType ( 'noMic' ) ; } setTimeout ( this . checkMic . bind ( this ) , 5 * 1000 ) ; } else { if ( participants . local . muteAudio ) { setTimeout ( this . checkMic . bind ( this ) , 5 * 1000 ) ; } this . clear ( 'noMic' ) ; this . clear ( 'micPermission' ) ; this . checkRemote ( ) ; } } checkRemote ( ) { if ( participants . remote . size > 0 ) { setTimeout ( this . checkChannel . bind ( this ) , 3 * 1000 ) ; } else { setTimeout ( this . checkRemote . bind ( this ) , 1 * 1000 ) ; } } @ action checkChannel ( ) { if ( participants . remote . size > 0 ) { if ( ! connection . conference . _jitsiConference ?. rtc . _channel ?. isOpen ( ) ) { this . setType ( 'channel' ) ; setTimeout ( this . checkChannel . bind ( this ) , 5 * 1000 ) ; } else { this . clear ( 'channel' ) ; } } else { this . checkRemote ( ) ; } } canvas : HTMLCanvasElement | undefined = undefined ; oscillator : OscillatorNode | undefined = undefined ; startTestBot ( ) { participants . local . muteAudio = false ; participants . local . muteVideo = false ; let counter = 0 ; const ctxA = new AudioContext ( ) ; this . oscillator = ctxA . createOscillator ( ) ; this . oscillator . type = 'triangle' ; const destination = ctxA . createMediaStreamDestination ( ) ; this . oscillator . connect ( destination ) ; this . oscillator . start ( ) ; this . canvas = document . createElement ( 'canvas' ) ; const width = 240 ; const height = 240 ; this . canvas . style . width = `${ width }px` ; this . canvas . style . height = `${ height }px` ; const ctx = this . canvas . getContext ( '2d' ) ; const center = [ Math . random ( ) * MAP_SIZE / 2 , ( Math . random ( ) - 0.5 ) * MAP_SIZE ] ; const draw = ( ) => { if ( ! ctx || ! ctxA ) { return ; } this . oscillator ?. frequency . setValueAtTime ( 440 + counter % 440 , ctxA . currentTime ) ; const colors = [ 'green' , 'blue' ] ; const nearest = priorityCalculator . tracksToAccept [ 0 ] . length ? participants . remote . get ( priorityCalculator . tracksToAccept [ 0 ] [ 0 ] . endpointId ) : undefined ; if ( nearest && ! nearest ?. tracks . avatarOk ) { colors [ 0 ] = 'yellow' ; } if ( nearest && nearest ?. tracks . audio ?. getTrack ( ) . muted ) { colors [ 1 ] = 'red' ; } ctx . fillStyle = colors [ 0 ] ; ctx . beginPath ( ) ; ctx . ellipse ( width * 0.63 , height * 0.33 , width * 0.1 , height * 0.4 , counter / 20 , 0 , Math . PI * 2 ) ; ctx . fill ( ) ; ctx . fillStyle = colors [ 1 ] ; ctx . beginPath ( ) ; ctx . ellipse ( width * 0.63 , height * 0.33 , width * 0.1 , height * 0.4 , - counter / 20 , 0 , Math . PI * 2 ) ; ctx . fill ( ) ; counter += 1 ; } ; setInterval ( draw , 1000 / 20 ) ; const chars = '01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijlkmnopqrstuvwxyz' ; const randChar = ( ) => chars . substr ( Math . floor ( Math . random ( ) * chars . length ) , 1 ) ; participants . local . information = defaultInformation ; participants . local . information . name = `testBot ${ randChar ( ) }${ randChar ( ) }${ randChar ( ) }` ; participants . local . sendInformation ( ) ; participants . local . pose . position = center as [ number , number ] ; const move = ( ) => { participants . local . pose . position = addV2 ( center , mulV2 ( 100 , [ Math . cos ( counter / 60 ) , Math . sin ( counter / 60 ) ] ) ) ; } ; const win = window as any ; if ( win . requestIdleCallback ) { const moveTask = ( ) => { setTimeout ( ( ) => { move ( ) ; win . requestIdleCallback ( moveTask ) ; } , 50 ) ; } ; moveTask ( ) ; } else { setInterval ( move , 1000 ) ; } const vidoeStream = ( this . canvas as any ) . captureStream ( 20 ) as MediaStream ; const audioStream = destination . stream ; const stream = new MediaStream ( ) ; stream . addTrack ( audioStream . getAudioTracks ( ) [ 0 ] ) ; stream . addTrack ( vidoeStream . getVideoTracks ( ) [ 0 ] ) ; const tracks = createJitisLocalTracksFromStream ( stream ) ; connection . conference . setLocalCameraTrack ( tracks [ 0 ] ) ; connection . conference . setLocalMicTrack ( tracks [ 1 ] ) ; } } const errorInfo = new ErrorInfo ( ) ; declare const d ; d . error = errorInfo ; export default errorInfo ;