2018-02-17 03:18:53 +08:00
import React , { Component } from 'react' ;
import { defineMessages , injectIntl } from 'react-intl' ;
2019-04-19 02:48:39 +08:00
import { Session } from 'meteor/session' ;
2018-02-17 03:18:53 +08:00
import { notify } from '/imports/ui/services/notification' ;
2018-05-03 01:53:53 +08:00
import VisibilityEvent from '/imports/utils/visibilityEvent' ;
2018-07-28 04:42:45 +08:00
import { fetchWebRTCMappedStunTurnServers } from '/imports/utils/fetchStunTurnServers' ;
import ReconnectingWebSocket from 'reconnecting-websocket' ;
2018-07-12 06:03:56 +08:00
import logger from '/imports/startup/client/logger' ;
2019-01-11 07:24:25 +08:00
import browser from 'browser-detect' ;
2019-04-12 22:11:36 +08:00
import {
2019-04-22 20:11:16 +08:00
updateCurrentWebcamsConnection ,
2019-04-12 22:11:36 +08:00
getCurrentWebcams ,
deleteWebcamConnection ,
newWebcamConnection ,
updateWebcamStats ,
} from '/imports/ui/services/network-information/index' ;
2019-01-11 07:24:25 +08:00
import { tryGenerateIceCandidates } from '../../../utils/safari-webrtc' ;
2019-01-08 00:54:37 +08:00
import Auth from '/imports/ui/services/auth' ;
2018-02-17 03:18:53 +08:00
2018-02-19 12:23:05 +08:00
import VideoService from './service' ;
2018-04-12 02:50:14 +08:00
import VideoList from './video-list/component' ;
2018-02-17 03:18:53 +08:00
2019-06-13 04:27:49 +08:00
const ENABLE _NETWORK _MONITORING = Meteor . settings . public . networkMonitoring . enableNetworkMonitoring ;
2019-04-09 06:07:26 +08:00
const CAMERA _PROFILES = Meteor . settings . public . kurento . cameraProfiles ;
2018-08-21 02:08:10 +08:00
2018-07-21 03:34:50 +08:00
const intlClientErrors = defineMessages ( {
2018-02-17 03:18:53 +08:00
iceCandidateError : {
id : 'app.video.iceCandidateError' ,
description : 'Error message for ice candidate fail' ,
} ,
chromeExtensionError : {
id : 'app.video.chromeExtensionError' ,
description : 'Error message for Chrome Extension not installed' ,
} ,
chromeExtensionErrorLink : {
id : 'app.video.chromeExtensionErrorLink' ,
description : 'Error message for Chrome Extension not installed' ,
2018-03-20 01:52:39 +08:00
} ,
2018-05-17 02:15:46 +08:00
permissionError : {
id : 'app.video.permissionError' ,
description : 'Error message for webcam permission' ,
} ,
2018-05-17 00:53:06 +08:00
NotFoundError : {
id : 'app.video.notFoundError' ,
description : 'error message when can not get webcam video' ,
} ,
NotAllowedError : {
id : 'app.video.notAllowed' ,
description : 'error message when webcam had permission denied' ,
} ,
NotSupportedError : {
id : 'app.video.notSupportedError' ,
description : 'error message when origin do not have ssl valid' ,
} ,
2018-05-17 02:15:46 +08:00
NotReadableError : {
id : 'app.video.notReadableError' ,
2018-05-17 21:23:18 +08:00
description : 'error message When the webcam is being used by other software' ,
} ,
2018-08-13 06:39:39 +08:00
iceConnectionStateError : {
id : 'app.video.iceConnectionStateError' ,
description : 'Error message for ice connection state being failed' ,
} ,
2018-12-18 01:45:57 +08:00
mediaFlowTimeout : {
id : 'app.video.mediaFlowTimeout1020' ,
description : 'Error message when media could not go through the server within the specified period' ,
} ,
2018-07-21 03:34:50 +08:00
} ) ;
const intlSFUErrors = defineMessages ( {
2000 : {
id : 'app.sfu.mediaServerConnectionError2000' ,
description : 'Error message fired when the SFU cannot connect to the media server' ,
} ,
2001 : {
id : 'app.sfu.mediaServerOffline2001' ,
2018-12-18 01:45:57 +08:00
description : 'error message when SFU is offline' ,
2018-05-17 02:15:46 +08:00
} ,
2018-07-21 03:34:50 +08:00
2002 : {
id : 'app.sfu.mediaServerNoResources2002' ,
description : 'Error message fired when the media server lacks disk, CPU or FDs' ,
} ,
2003 : {
id : 'app.sfu.mediaServerRequestTimeout2003' ,
2018-08-13 06:40:53 +08:00
description : 'Error message fired when requests are timing out due to lack of resources' ,
2018-07-21 03:34:50 +08:00
} ,
2021 : {
id : 'app.sfu.serverIceGatheringFailed2021' ,
description : 'Error message fired when the server cannot enact ICE gathering' ,
} ,
2022 : {
id : 'app.sfu.serverIceStateFailed2022' ,
description : 'Error message fired when the server endpoint transitioned to a FAILED ICE state' ,
} ,
2018-12-18 01:45:57 +08:00
2200 : {
id : 'app.sfu.mediaGenericError2200' ,
description : 'Error message fired when the SFU component generated a generic error' ,
} ,
2018-07-21 03:34:50 +08:00
2202 : {
id : 'app.sfu.invalidSdp2202' ,
description : 'Error message fired when the clients provides an invalid SDP' ,
} ,
2203 : {
id : 'app.sfu.noAvailableCodec2203' ,
description : 'Error message fired when the server has no available codec for the client' ,
2018-08-13 06:40:53 +08:00
} ,
2018-02-17 03:18:53 +08:00
} ) ;
2018-05-23 04:44:30 +08:00
const CAMERA _SHARE _FAILED _WAIT _TIME = 15000 ;
const MAX _CAMERA _SHARE _FAILED _WAIT _TIME = 60000 ;
2018-05-03 21:57:03 +08:00
const PING _INTERVAL = 15000 ;
2018-02-17 03:18:53 +08:00
class VideoProvider extends Component {
2019-04-24 04:23:32 +08:00
static notifyError ( message ) {
notify ( message , 'error' , 'video' ) ;
}
2018-02-17 03:18:53 +08:00
constructor ( props ) {
super ( props ) ;
this . state = {
2018-03-20 01:52:39 +08:00
socketOpen : false ,
2018-05-03 03:03:36 +08:00
stats : [ ] ,
2018-02-17 03:18:53 +08:00
} ;
// Set a valid bbb-webrtc-sfu application server socket in the settings
2019-01-08 00:54:37 +08:00
this . ws = new ReconnectingWebSocket ( Auth . authenticateURL ( Meteor . settings . public . kurento . wsUrl ) ) ;
2018-02-17 03:18:53 +08:00
this . wsQueue = [ ] ;
2018-05-03 01:53:53 +08:00
this . visibility = new VisibilityEvent ( ) ;
2018-05-23 04:44:30 +08:00
this . restartTimeout = { } ;
this . restartTimer = { } ;
2018-02-17 03:18:53 +08:00
this . webRtcPeers = { } ;
2018-04-28 04:53:02 +08:00
this . monitoredTracks = { } ;
2018-07-10 05:29:27 +08:00
this . videoTags = { } ;
this . sharedWebcam = false ;
2018-02-17 03:18:53 +08:00
2018-12-18 06:19:26 +08:00
this . createVideoTag = this . createVideoTag . bind ( this ) ;
this . getStats = this . getStats . bind ( this ) ;
this . stopGettingStats = this . stopGettingStats . bind ( this ) ;
2018-02-17 03:18:53 +08:00
this . onWsOpen = this . onWsOpen . bind ( this ) ;
this . onWsClose = this . onWsClose . bind ( this ) ;
this . onWsMessage = this . onWsMessage . bind ( this ) ;
this . unshareWebcam = this . unshareWebcam . bind ( this ) ;
this . shareWebcam = this . shareWebcam . bind ( this ) ;
2018-05-03 01:53:53 +08:00
this . pauseViewers = this . pauseViewers . bind ( this ) ;
this . unpauseViewers = this . unpauseViewers . bind ( this ) ;
2018-05-26 03:24:41 +08:00
this . customGetStats = this . customGetStats . bind ( this ) ;
2018-05-03 01:53:53 +08:00
}
2018-02-17 03:18:53 +08:00
componentWillMount ( ) {
2018-12-18 06:19:26 +08:00
this . ws . onopen = this . onWsOpen ;
2018-12-18 01:45:57 +08:00
this . ws . onclose = this . onWsClose ;
2018-02-17 03:18:53 +08:00
2018-04-14 03:16:06 +08:00
window . addEventListener ( 'online' , this . openWs ) ;
2018-02-17 03:18:53 +08:00
window . addEventListener ( 'offline' , this . onWsClose ) ;
}
componentDidMount ( ) {
2019-04-24 04:23:32 +08:00
const { onMount } = this . props ;
onMount ( ) ;
2019-01-15 08:45:32 +08:00
this . checkIceConnectivity ( ) ;
2018-04-14 03:16:06 +08:00
document . addEventListener ( 'joinVideo' , this . shareWebcam ) ; // TODO find a better way to do this
document . addEventListener ( 'exitVideo' , this . unshareWebcam ) ;
2018-12-18 01:45:57 +08:00
this . ws . onmessage = this . onWsMessage ;
2018-11-17 03:03:20 +08:00
window . addEventListener ( 'beforeunload' , this . unshareWebcam ) ;
2018-05-03 01:53:53 +08:00
this . visibility . onVisible ( this . unpauseViewers ) ;
this . visibility . onHidden ( this . pauseViewers ) ;
2019-04-12 22:11:36 +08:00
2019-06-13 04:27:49 +08:00
if ( ENABLE _NETWORK _MONITORING ) {
2019-05-25 03:55:35 +08:00
this . currentWebcamsStatsInterval = setInterval ( ( ) => {
const currentWebcams = getCurrentWebcams ( ) ;
if ( ! currentWebcams ) return ;
2019-04-12 22:11:36 +08:00
2019-05-25 03:55:35 +08:00
const { payload } = currentWebcams ;
2019-04-12 22:11:36 +08:00
2019-05-25 03:55:35 +08:00
payload . forEach ( ( id ) => {
const peer = this . webRtcPeers [ id ] ;
2019-04-12 22:11:36 +08:00
2019-05-25 03:55:35 +08:00
const hasLocalStream = peer && peer . started === true
&& peer . peerConnection . getLocalStreams ( ) . length > 0 ;
const hasRemoteStream = peer && peer . started === true
&& peer . peerConnection . getRemoteStreams ( ) . length > 0 ;
2019-04-12 22:11:36 +08:00
2019-05-25 03:55:35 +08:00
if ( hasLocalStream ) {
2019-06-08 04:45:54 +08:00
this . customGetStats ( peer . peerConnection ,
peer . peerConnection . getLocalStreams ( ) [ 0 ] . getVideoTracks ( ) [ 0 ] ,
( stats => updateWebcamStats ( id , stats ) ) , true ) ;
2019-05-25 03:55:35 +08:00
} else if ( hasRemoteStream ) {
2019-06-08 04:45:54 +08:00
this . customGetStats ( peer . peerConnection ,
peer . peerConnection . getRemoteStreams ( ) [ 0 ] . getVideoTracks ( ) [ 0 ] ,
( stats => updateWebcamStats ( id , stats ) ) , true ) ;
2019-05-25 03:55:35 +08:00
}
} ) ;
} , 5000 ) ;
}
2018-02-17 03:18:53 +08:00
}
2018-04-12 02:50:14 +08:00
componentWillUpdate ( { users , userId } ) {
const usersSharingIds = users . map ( u => u . id ) ;
const usersConnected = Object . keys ( this . webRtcPeers ) ;
const usersToConnect = usersSharingIds . filter ( id => ! usersConnected . includes ( id ) ) ;
const usersToDisconnect = usersConnected . filter ( id => ! usersSharingIds . includes ( id ) ) ;
usersToConnect . forEach ( id => this . createWebRTCPeer ( id , userId === id ) ) ;
2018-04-19 02:16:26 +08:00
usersToDisconnect . forEach ( id => this . stopWebRTCPeer ( id ) ) ;
2018-04-12 02:50:14 +08:00
}
2019-06-03 22:05:09 +08:00
componentDidUpdate ( prevProps ) {
const { users } = this . props ;
if ( users . length !== prevProps . users . length ) window . dispatchEvent ( new Event ( 'videoListUsersChange' ) ) ;
}
2018-02-17 03:18:53 +08:00
componentWillUnmount ( ) {
document . removeEventListener ( 'joinVideo' , this . shareWebcam ) ;
document . removeEventListener ( 'exitVideo' , this . unshareWebcam ) ;
2018-12-18 01:45:57 +08:00
this . ws . onmessage = null ;
this . ws . onopen = null ;
this . ws . onclose = null ;
2018-02-17 03:18:53 +08:00
2018-04-14 03:16:06 +08:00
window . removeEventListener ( 'online' , this . openWs ) ;
2018-02-17 03:18:53 +08:00
window . removeEventListener ( 'offline' , this . onWsClose ) ;
2018-12-21 03:03:09 +08:00
window . removeEventListener ( 'beforeunload' , this . unshareWebcam ) ;
2018-02-17 03:18:53 +08:00
2018-05-03 01:53:53 +08:00
this . visibility . removeEventListeners ( ) ;
2018-02-17 03:18:53 +08:00
// Unshare user webcam
2018-07-10 05:29:27 +08:00
if ( this . sharedWebcam ) {
2018-02-17 03:18:53 +08:00
this . unshareWebcam ( ) ;
2019-04-19 02:48:39 +08:00
Session . set ( 'userWasInWebcam' , true ) ;
2018-02-17 03:18:53 +08:00
}
Object . keys ( this . webRtcPeers ) . forEach ( ( id ) => {
2018-04-27 07:35:05 +08:00
this . stopGettingStats ( id ) ;
2018-04-19 02:16:26 +08:00
this . stopWebRTCPeer ( id ) ;
2018-02-17 03:18:53 +08:00
} ) ;
2019-04-12 22:11:36 +08:00
clearInterval ( this . currentWebcamsStatsInterval ) ;
2018-02-17 03:18:53 +08:00
// Close websocket connection to prevent multiple reconnects from happening
2018-04-26 23:39:24 +08:00
this . ws . close ( ) ;
2018-02-17 03:18:53 +08:00
}
2018-12-18 06:19:26 +08:00
onWsMessage ( msg ) {
const parsedMessage = JSON . parse ( msg . data ) ;
2018-02-17 03:18:53 +08:00
2019-01-22 04:05:52 +08:00
if ( parsedMessage . id === 'pong' ) return ;
2018-12-18 06:19:26 +08:00
switch ( parsedMessage . id ) {
case 'startResponse' :
this . startResponse ( parsedMessage ) ;
break ;
2018-05-03 21:57:03 +08:00
2018-12-18 06:19:26 +08:00
case 'playStart' :
this . handlePlayStart ( parsedMessage ) ;
break ;
case 'playStop' :
this . handlePlayStop ( parsedMessage ) ;
break ;
case 'iceCandidate' :
this . handleIceCandidate ( parsedMessage ) ;
break ;
case 'pong' :
break ;
case 'error' :
default :
this . handleSFUError ( parsedMessage ) ;
break ;
}
2018-02-17 03:18:53 +08:00
}
2018-12-18 06:19:26 +08:00
onWsClose ( ) {
2019-04-24 02:26:28 +08:00
this . logger ( 'debug' , '------ Websocket connection closed.' , 'video_provider_onwsclose' , { topic : 'ws' } ) ;
2018-02-17 03:18:53 +08:00
2018-05-03 21:57:03 +08:00
clearInterval ( this . pingInterval ) ;
2018-03-20 01:52:39 +08:00
2018-12-18 01:45:57 +08:00
if ( this . sharedWebcam ) {
this . unshareWebcam ( ) ;
}
2018-03-20 01:52:39 +08:00
this . setState ( { socketOpen : false } ) ;
2018-02-17 03:18:53 +08:00
}
2018-12-18 06:19:26 +08:00
onWsOpen ( ) {
2019-04-24 02:26:28 +08:00
this . logger ( 'debug' , '------ Websocket connection opened.' , 'video_provider_onwsopen' , { topic : 'ws' } ) ;
2018-12-18 06:19:26 +08:00
// -- Resend queued messages that happened when socket was not connected
while ( this . wsQueue . length > 0 ) {
this . sendMessage ( this . wsQueue . pop ( ) ) ;
}
this . pingInterval = setInterval ( this . ping . bind ( this ) , PING _INTERVAL ) ;
this . setState ( { socketOpen : true } ) ;
2018-05-03 21:57:03 +08:00
}
2018-12-18 06:19:26 +08:00
getStats ( id , video , callback ) {
const peer = this . webRtcPeers [ id ] ;
2018-02-17 03:18:53 +08:00
2018-12-18 06:19:26 +08:00
const hasLocalStream = peer && peer . started === true
&& peer . peerConnection . getLocalStreams ( ) . length > 0 ;
const hasRemoteStream = peer && peer . started === true
&& peer . peerConnection . getRemoteStreams ( ) . length > 0 ;
2018-02-17 03:18:53 +08:00
2018-12-18 06:19:26 +08:00
if ( hasLocalStream ) {
this . monitorTrackStart ( peer . peerConnection ,
peer . peerConnection . getLocalStreams ( ) [ 0 ] . getVideoTracks ( ) [ 0 ] , true , callback ) ;
} else if ( hasRemoteStream ) {
this . monitorTrackStart ( peer . peerConnection ,
peer . peerConnection . getRemoteStreams ( ) [ 0 ] . getVideoTracks ( ) [ 0 ] , false , callback ) ;
}
}
2018-02-17 03:18:53 +08:00
2019-01-15 08:45:32 +08:00
checkIceConnectivity ( ) {
// Webkit ICE restrictions demand a capture device permission to release
// host candidates
if ( browser ( ) . name === 'safari' ) {
const { intl } = this . props ;
2019-04-24 04:23:32 +08:00
tryGenerateIceCandidates ( ) . catch ( ( ) => {
VideoProvider . notifyError ( intl . formatMessage ( intlSFUErrors [ 2021 ] ) ) ;
2019-01-15 08:45:32 +08:00
} ) ;
}
}
2019-01-22 04:05:52 +08:00
2019-04-24 02:26:28 +08:00
logger ( type , message , logCode , options = { } ) {
2018-12-18 06:19:26 +08:00
const { userId , userName } = this . props ;
const topic = options . topic || 'video' ;
2019-04-24 03:57:31 +08:00
logger [ type ] ( ` ${ JSON . stringify ( Object . assign ( options , {
userId , userName , topic , logCode ,
} ) ) } , [ $ { topic } ] $ { message } ` );
2018-12-18 06:19:26 +08:00
}
2018-02-17 03:18:53 +08:00
2018-12-18 06:19:26 +08:00
_sendPauseStream ( id , role , state ) {
this . sendMessage ( {
cameraId : id ,
id : 'pause' ,
type : 'video' ,
role ,
state ,
} ) ;
}
2018-02-17 03:18:53 +08:00
2018-12-18 06:19:26 +08:00
pauseViewers ( ) {
const { userId } = this . props ;
2019-04-24 02:26:28 +08:00
this . logger ( 'debug' , 'Calling pause in viewer streams' , 'video_provider_pause_viewers' ) ;
2018-05-03 21:57:03 +08:00
2018-12-18 06:19:26 +08:00
Object . keys ( this . webRtcPeers ) . forEach ( ( id ) => {
if ( userId !== id && this . webRtcPeers [ id ] . started ) {
this . _sendPauseStream ( id , 'viewer' , true ) ;
}
} ) ;
}
unpauseViewers ( ) {
const { userId } = this . props ;
2019-04-24 02:26:28 +08:00
this . logger ( 'debug' , 'Calling un-pause in viewer streams' , 'video_provider_unpause_viewers' ) ;
2018-12-18 06:19:26 +08:00
Object . keys ( this . webRtcPeers ) . forEach ( ( id ) => {
if ( id !== userId && this . webRtcPeers [ id ] . started ) {
this . _sendPauseStream ( id , 'viewer' , false ) ;
}
} ) ;
}
ping ( ) {
const message = {
id : 'ping' ,
} ;
this . sendMessage ( message ) ;
2018-02-17 03:18:53 +08:00
}
sendMessage ( message ) {
2018-06-15 03:20:14 +08:00
const { ws } = this ;
2018-02-17 03:18:53 +08:00
if ( this . connectedToMediaServer ( ) ) {
const jsonMessage = JSON . stringify ( message ) ;
ws . send ( jsonMessage , ( error ) => {
if ( error ) {
2019-04-24 02:26:28 +08:00
this . logger ( 'error' , ` client: Websocket error ' ${ error } ' on message ' ${ message . id } ' ` , 'video_provider_ws_error' , { topic : 'ws' } ) ;
2018-02-17 03:18:53 +08:00
}
} ) ;
2019-04-24 04:23:32 +08:00
} else if ( message . id !== 'stop' ) {
2018-02-17 03:18:53 +08:00
// No need to queue video stop messages
2019-04-24 04:23:32 +08:00
this . wsQueue . push ( message ) ;
2018-02-17 03:18:53 +08:00
}
}
connectedToMediaServer ( ) {
return this . ws . readyState === WebSocket . OPEN ;
}
startResponse ( message ) {
const id = message . cameraId ;
2018-05-23 01:07:15 +08:00
const peer = this . webRtcPeers [ id ] ;
2018-02-17 03:18:53 +08:00
2019-04-24 02:26:28 +08:00
this . logger ( 'debug' , 'SDP answer received from server. Processing ...' , 'video_provider_sdp_received' ,
{ cameraId : id , sdpAnswer : message . sdpAnswer } ) ;
2018-02-17 03:18:53 +08:00
2018-07-12 00:32:19 +08:00
if ( peer ) {
peer . processAnswer ( message . sdpAnswer , ( error ) => {
if ( error ) {
2019-04-24 02:26:28 +08:00
return this . logger ( 'debug' , JSON . stringify ( error ) , 'video_provider_peer_process_answer' , { cameraId : id } ) ;
2018-07-12 00:32:19 +08:00
}
2018-12-22 03:47:15 +08:00
peer . didSDPAnswered = true ;
this . _processIceQueue ( peer , id ) ;
2019-04-24 04:23:32 +08:00
return true ;
2018-07-12 00:32:19 +08:00
} ) ;
} else {
2019-04-24 02:26:28 +08:00
this . logger ( 'warn' , '[startResponse] Message arrived after the peer was already thrown out, discarding it...' , 'video_provider_no_peer' ) ;
2018-07-12 00:32:19 +08:00
}
2018-02-17 03:18:53 +08:00
}
handleIceCandidate ( message ) {
const webRtcPeer = this . webRtcPeers [ message . cameraId ] ;
2019-04-24 02:26:28 +08:00
this . logger ( 'debug' , 'Received remote ice candidate' , 'video_provider_ice_candidate_received' , { topic : 'ice' , candidate : message . candidate } ) ;
2018-06-20 01:36:12 +08:00
2018-02-17 03:18:53 +08:00
if ( webRtcPeer ) {
if ( webRtcPeer . didSDPAnswered ) {
webRtcPeer . addIceCandidate ( message . candidate , ( err ) => {
if ( err ) {
2019-04-24 02:26:28 +08:00
return this . logger ( 'error' , ` Error adding candidate: ${ err } ` , 'video_provider_ice_candidate_cant_add' , { cameraId : message . cameraId } ) ;
2018-02-17 03:18:53 +08:00
}
2019-04-24 04:23:32 +08:00
return true ;
2018-02-17 03:18:53 +08:00
} ) ;
} else {
2018-07-21 03:34:50 +08:00
if ( webRtcPeer . iceQueue == null ) {
webRtcPeer . iceQueue = [ ] ;
}
2018-02-17 03:18:53 +08:00
webRtcPeer . iceQueue . push ( message . candidate ) ;
}
} else {
2019-04-24 02:26:28 +08:00
this . logger ( 'warn' , ' [iceCandidate] Message arrived after the peer was already thrown out, discarding it...' , 'video_provider_ice_candidate_arrived_late' , { cameraId : message . cameraId } ) ;
2018-02-17 03:18:53 +08:00
}
}
2018-12-18 22:54:17 +08:00
stopWebRTCPeer ( id , restarting = false ) {
2019-04-24 02:26:28 +08:00
this . logger ( 'info' , 'Stopping webcam' , 'video_provider_stopping_webcam' , { cameraId : id } ) ;
2018-06-15 03:20:14 +08:00
const { userId } = this . props ;
2018-04-19 02:16:26 +08:00
const shareWebcam = id === userId ;
2018-02-17 03:18:53 +08:00
2018-08-14 04:26:45 +08:00
// in this case, 'closed' state is not caused by an error;
// we stop listening to prevent this from being treated as an error
2018-11-10 00:25:48 +08:00
if ( this . webRtcPeers [ id ] && this . webRtcPeers [ id ] . peerConnection ) {
2018-08-21 02:08:10 +08:00
this . webRtcPeers [ id ] . peerConnection . oniceconnectionstatechange = null ;
}
2018-08-14 04:26:45 +08:00
2018-04-12 02:50:14 +08:00
if ( shareWebcam ) {
this . unshareWebcam ( ) ;
}
2018-04-14 03:16:06 +08:00
this . sendMessage ( {
type : 'video' ,
2018-04-19 02:16:26 +08:00
role : shareWebcam ? 'share' : 'viewer' ,
2018-04-14 03:16:06 +08:00
id : 'stop' ,
cameraId : id ,
} ) ;
2018-12-18 01:45:57 +08:00
// Clear the shared camera media flow timeout when destroying it
2018-12-18 22:54:17 +08:00
if ( ! restarting ) {
if ( this . restartTimeout [ id ] ) {
clearTimeout ( this . restartTimeout [ id ] ) ;
}
2018-12-18 01:45:57 +08:00
2018-12-18 22:54:17 +08:00
if ( this . restartTimer [ id ] ) {
delete this . restartTimer [ id ] ;
}
2018-12-18 01:45:57 +08:00
}
2018-05-23 04:44:30 +08:00
2018-04-19 02:16:26 +08:00
this . destroyWebRTCPeer ( id ) ;
}
2018-02-17 03:18:53 +08:00
2018-04-19 02:16:26 +08:00
destroyWebRTCPeer ( id ) {
const webRtcPeer = this . webRtcPeers [ id ] ;
2018-02-17 03:18:53 +08:00
if ( webRtcPeer ) {
2019-04-24 02:26:28 +08:00
this . logger ( 'info' , 'Stopping WebRTC peer' , 'video_provider_destroy_webrtc_peers' , { cameraId : id } ) ;
2018-11-10 00:25:48 +08:00
if ( typeof webRtcPeer . dispose === 'function' ) {
webRtcPeer . dispose ( ) ;
}
2018-02-17 03:18:53 +08:00
delete this . webRtcPeers [ id ] ;
2019-06-13 04:27:49 +08:00
if ( ENABLE _NETWORK _MONITORING ) {
2019-05-25 03:55:35 +08:00
deleteWebcamConnection ( id ) ;
updateCurrentWebcamsConnection ( this . webRtcPeers ) ;
}
2018-02-17 03:18:53 +08:00
} else {
2019-04-24 02:26:28 +08:00
this . logger ( 'warn' , 'No WebRTC peer to stop (not an error)' , 'video_provider_no_peer_to_destroy' , { cameraId : id } ) ;
2018-02-17 03:18:53 +08:00
}
}
2018-07-10 05:29:27 +08:00
async createWebRTCPeer ( id , shareWebcam ) {
2018-08-27 15:37:25 +08:00
const { meetingId , sessionToken , voiceBridge } = this . props ;
2018-07-10 05:29:27 +08:00
let iceServers = [ ] ;
2018-02-17 03:18:53 +08:00
2018-11-10 00:25:48 +08:00
// Check if the peer is already being processed
if ( this . webRtcPeers [ id ] ) {
return ;
}
this . webRtcPeers [ id ] = { } ;
2018-07-10 05:29:27 +08:00
try {
iceServers = await fetchWebRTCMappedStunTurnServers ( sessionToken ) ;
} catch ( error ) {
2019-04-24 02:26:28 +08:00
this . logger ( 'error' , 'Video provider failed to fetch ice servers, using default' , 'video_provider_missing_ice_servers' ) ;
2018-07-10 05:29:27 +08:00
} finally {
2019-04-09 06:07:26 +08:00
const profileId = Session . get ( 'WebcamProfileId' ) || '' ;
const cameraProfile = CAMERA _PROFILES . find ( profile => profile . id === profileId )
|| CAMERA _PROFILES . find ( profile => profile . default )
|| CAMERA _PROFILES [ 0 ] ;
const { constraints } = cameraProfile ;
2018-11-07 07:10:56 +08:00
if ( Session . get ( 'WebcamDeviceId' ) ) {
2019-04-09 06:07:26 +08:00
constraints . deviceId = { exact : Session . get ( 'WebcamDeviceId' ) } ;
2018-11-07 07:10:56 +08:00
}
2018-07-10 05:29:27 +08:00
const options = {
mediaConstraints : {
audio : false ,
2019-04-09 06:07:26 +08:00
video : constraints ,
2018-07-10 05:29:27 +08:00
} ,
onicecandidate : this . _getOnIceCandidateCallback ( id , shareWebcam ) ,
} ;
2018-02-17 05:11:59 +08:00
2018-07-10 05:29:27 +08:00
if ( iceServers . length > 0 ) {
options . configuration = { } ;
options . configuration . iceServers = iceServers ;
}
2018-04-12 02:50:14 +08:00
2018-10-04 02:39:55 +08:00
let WebRtcPeerObj ;
2018-07-10 05:29:27 +08:00
if ( shareWebcam ) {
WebRtcPeerObj = window . kurentoUtils . WebRtcPeer . WebRtcPeerSendonly ;
this . shareWebcam ( ) ;
2018-10-04 02:39:55 +08:00
} else {
WebRtcPeerObj = window . kurentoUtils . WebRtcPeer . WebRtcPeerRecvonly ;
2018-07-10 05:29:27 +08:00
}
2018-05-23 04:44:30 +08:00
2018-07-10 05:29:27 +08:00
this . webRtcPeers [ id ] = new WebRtcPeerObj ( options , ( error ) => {
const peer = this . webRtcPeers [ id ] ;
2018-04-14 03:16:06 +08:00
2018-07-10 05:29:27 +08:00
peer . started = false ;
peer . attached = false ;
peer . didSDPAnswered = false ;
2018-07-21 03:34:50 +08:00
if ( peer . iceQueue == null ) {
peer . iceQueue = [ ] ;
}
2018-02-17 03:18:53 +08:00
2018-07-10 05:29:27 +08:00
if ( error ) {
return this . _webRTCOnError ( error , id , shareWebcam ) ;
2018-02-17 03:18:53 +08:00
}
2018-07-10 05:29:27 +08:00
peer . generateOffer ( ( errorGenOffer , offerSdp ) => {
if ( errorGenOffer ) {
return this . _webRTCOnError ( errorGenOffer , id , shareWebcam ) ;
}
2019-04-24 21:37:30 +08:00
this . logger ( 'debug' , ` Invoking SDP offer callback function ${ window . location . host } ` , 'video_provider_sdp_offer_callback' , { cameraId : id , offerSdp } ) ;
2018-04-05 00:15:20 +08:00
2018-07-10 05:29:27 +08:00
const message = {
type : 'video' ,
role : shareWebcam ? 'share' : 'viewer' ,
id : 'start' ,
sdpOffer : offerSdp ,
cameraId : id ,
meetingId ,
2018-08-27 15:37:25 +08:00
voiceBridge ,
2018-07-10 05:29:27 +08:00
} ;
this . sendMessage ( message ) ;
2019-04-24 04:23:32 +08:00
return true ;
2018-07-10 05:29:27 +08:00
} ) ;
2019-04-24 04:23:32 +08:00
return true ;
2018-02-17 03:18:53 +08:00
} ) ;
2018-11-10 00:25:48 +08:00
if ( this . webRtcPeers [ id ] . peerConnection ) {
2019-04-24 04:23:32 +08:00
this . webRtcPeers [ id ]
. peerConnection
. oniceconnectionstatechange = this . _getOnIceConnectionStateChangeCallback ( id ) ;
2018-11-10 00:25:48 +08:00
}
2019-06-13 04:27:49 +08:00
if ( ENABLE _NETWORK _MONITORING ) {
2019-05-25 03:55:35 +08:00
newWebcamConnection ( id ) ;
updateCurrentWebcamsConnection ( this . webRtcPeers ) ;
}
2018-07-10 05:29:27 +08:00
}
2018-02-17 03:18:53 +08:00
}
2018-12-18 06:19:26 +08:00
_getWebRTCStartTimeout ( id , shareWebcam ) {
const { intl , userId } = this . props ;
2018-05-23 04:44:30 +08:00
return ( ) => {
2019-04-24 02:26:28 +08:00
this . logger ( 'error' , ` Camera share has not succeeded in ${ CAMERA _SHARE _FAILED _WAIT _TIME } ` , 'video_provider_cam_timeout' , { cameraId : id } ) ;
2018-05-23 04:44:30 +08:00
2018-12-18 06:19:26 +08:00
if ( userId === id ) {
2019-04-24 04:23:32 +08:00
VideoProvider . notifyError ( intl . formatMessage ( intlClientErrors . mediaFlowTimeout ) ) ;
2018-12-18 22:54:17 +08:00
this . stopWebRTCPeer ( id , false ) ;
2018-05-23 04:44:30 +08:00
} else {
2018-12-18 22:54:17 +08:00
// Subscribers try to reconnect according to their timers if media could
// not reach the server. That's why we pass the restarting flag as true
// to the stop procedure as to not destroy the timers
this . stopWebRTCPeer ( id , true ) ;
2018-05-23 04:44:30 +08:00
this . createWebRTCPeer ( id , shareWebcam ) ;
// Increment reconnect interval
2019-04-24 04:23:32 +08:00
this . restartTimer [ id ] = Math
. min ( 2 * this . restartTimer [ id ] , MAX _CAMERA _SHARE _FAILED _WAIT _TIME ) ;
2018-10-12 02:23:26 +08:00
2019-04-24 02:26:28 +08:00
this . logger ( 'info' , ` Reconnecting peer ${ id } with timer ` , 'video_provider_reconnecting_peer' , this . restartTimer ) ;
2018-05-23 04:44:30 +08:00
}
} ;
}
2018-06-20 01:36:12 +08:00
_processIceQueue ( peer , cameraId ) {
2018-05-23 04:44:30 +08:00
const { intl } = this . props ;
while ( peer . iceQueue . length ) {
const candidate = peer . iceQueue . shift ( ) ;
peer . addIceCandidate ( candidate , ( err ) => {
if ( err ) {
2019-04-24 04:23:32 +08:00
VideoProvider . notifyError ( intl . formatMessage ( intlClientErrors . iceCandidateError ) ) ;
2019-04-24 02:26:28 +08:00
return this . logger ( 'error' , ` Error adding candidate: ${ err } ` , 'video_provider_cant_add_candidate' , { cameraId } ) ;
2018-05-23 04:44:30 +08:00
}
2019-04-24 04:23:32 +08:00
return true ;
2018-05-23 04:44:30 +08:00
} ) ;
}
}
2018-12-18 06:19:26 +08:00
_webRTCOnError ( error , id ) {
2018-12-22 05:25:47 +08:00
const { intl , userId } = this . props ;
// We only display SFU connection errors to sharers, because it's guaranteed
// they should be connected. Viewers aren't connected synchronously related
// to the createWebRTCPeer procedure, so the error is ignored. If the connection
// closes unexpectedly, this error is deplayed globally in the onWsClose catch
if ( error === 2001 && userId !== id ) {
return ;
}
2018-05-23 04:44:30 +08:00
2019-04-24 02:26:28 +08:00
this . logger ( 'error' , ' WebRTC peerObj create error' , 'video_provider_webrtc_error_before' , { id , error } ) ;
2018-07-21 03:34:50 +08:00
const errorMessage = intlClientErrors [ error . name ]
2018-12-18 01:45:57 +08:00
|| intlSFUErrors [ error ] || intlClientErrors . permissionError ;
2019-04-24 04:23:32 +08:00
VideoProvider . notifyError ( intl . formatMessage ( errorMessage ) ) ;
2018-05-23 04:44:30 +08:00
this . stopWebRTCPeer ( id ) ;
2019-04-24 21:37:30 +08:00
this . logger ( 'error' , errorMessage , 'video_provider_webrtc_error_after' , { cameraId : id , errorMessage } ) ;
2018-05-23 04:44:30 +08:00
}
_getOnIceCandidateCallback ( id , shareWebcam ) {
const peer = this . webRtcPeers [ id ] ;
return ( candidate ) => {
// Setup a timeout only when ice first is generated
if ( ! this . restartTimeout [ id ] ) {
this . restartTimer [ id ] = this . restartTimer [ id ] || CAMERA _SHARE _FAILED _WAIT _TIME ;
2019-04-24 02:26:28 +08:00
this . logger ( 'debug' , ` Setting a camera connection restart in ${ this . restartTimer [ id ] } ` , 'video_provider_cam_restart' , { cameraId : id } ) ;
this . restartTimeout [ id ] = setTimeout ( this . _getWebRTCStartTimeout ( id , shareWebcam , peer ) ,
this . restartTimer [ id ] ) ;
2018-05-23 04:44:30 +08:00
}
2018-02-17 03:18:53 +08:00
2019-04-24 02:26:28 +08:00
this . logger ( 'debug' , 'Generated local ice candidate' , 'video_provider_generated_local_ice' , { topic : 'ice' , candidate } ) ;
2018-06-20 01:36:12 +08:00
2018-02-17 03:18:53 +08:00
const message = {
type : 'video' ,
role : shareWebcam ? 'share' : 'viewer' ,
id : 'onIceCandidate' ,
candidate ,
cameraId : id ,
} ;
2018-05-23 04:44:30 +08:00
this . sendMessage ( message ) ;
2018-03-20 01:52:39 +08:00
} ;
}
2018-02-17 03:18:53 +08:00
2018-08-13 06:39:39 +08:00
_getOnIceConnectionStateChangeCallback ( id ) {
const { intl } = this . props ;
const peer = this . webRtcPeers [ id ] ;
2018-12-18 06:19:26 +08:00
return ( ) => {
2018-08-13 06:39:39 +08:00
const connectionState = peer . peerConnection . iceConnectionState ;
2018-08-14 04:26:45 +08:00
if ( connectionState === 'failed' || connectionState === 'closed' ) {
// prevent the same error from being detected multiple times
peer . peerConnection . oniceconnectionstatechange = null ;
2019-04-24 02:26:28 +08:00
this . logger ( 'error' , ` ICE connection state id: ${ id } , connectionState: ${ connectionState } ` , 'video_provider_ice_connection_failed_state' ) ;
2018-08-13 06:39:39 +08:00
this . stopWebRTCPeer ( id ) ;
2019-04-24 04:23:32 +08:00
VideoProvider . notifyError ( intl . formatMessage ( intlClientErrors . iceConnectionStateError ) ) ;
2018-08-13 06:39:39 +08:00
}
2018-08-13 06:40:53 +08:00
} ;
2018-08-13 06:39:39 +08:00
}
2018-07-10 05:29:27 +08:00
attachVideoStream ( id ) {
2018-12-18 06:19:26 +08:00
const { userId } = this . props ;
2018-07-10 05:29:27 +08:00
const video = this . videoTags [ id ] ;
if ( video == null ) {
2019-04-24 02:26:28 +08:00
this . logger ( 'warn' , ` Peer ${ id } ${ userId } has not been started yet ` , 'video_provider_attach_video_stream' ) ;
2018-07-12 00:32:19 +08:00
return ;
2018-07-10 05:29:27 +08:00
}
if ( video . srcObject ) {
delete this . videoTags [ id ] ;
return ; // Skip if the stream is already attached
}
2018-02-17 03:18:53 +08:00
2018-12-18 06:19:26 +08:00
const isCurrent = id === userId ;
2018-04-12 02:50:14 +08:00
const peer = this . webRtcPeers [ id ] ;
2018-03-20 01:52:39 +08:00
2018-05-30 02:54:01 +08:00
const attachVideoStreamHelper = ( ) => {
2018-04-12 02:50:14 +08:00
const stream = isCurrent ? peer . getLocalStream ( ) : peer . getRemoteStream ( ) ;
video . pause ( ) ;
video . srcObject = stream ;
video . load ( ) ;
2018-05-23 04:55:24 +08:00
peer . attached = true ;
2018-07-10 05:29:27 +08:00
delete this . videoTags [ id ] ;
2018-04-12 02:50:14 +08:00
} ;
2018-05-23 04:55:24 +08:00
// If peer has started playing attach to tag, otherwise wait a while
2018-04-19 02:16:26 +08:00
if ( peer ) {
2018-05-23 04:55:24 +08:00
if ( peer . started ) {
2018-05-30 02:54:01 +08:00
attachVideoStreamHelper ( ) ;
2018-04-19 02:16:26 +08:00
}
2018-02-17 03:18:53 +08:00
2018-05-23 04:55:24 +08:00
// So we can start it later when we get a playStart
// or if we need to do a restart timeout
peer . videoTag = video ;
2018-04-19 02:16:26 +08:00
}
2018-02-17 03:18:53 +08:00
}
2018-07-10 05:29:27 +08:00
createVideoTag ( id , video ) {
const peer = this . webRtcPeers [ id ] ;
this . videoTags [ id ] = video ;
if ( peer ) {
this . attachVideoStream ( id ) ;
}
}
2019-05-24 02:25:31 +08:00
customGetStats ( peer , mediaStreamTrack , callback , monitoring = false ) {
2018-12-18 06:19:26 +08:00
const { stats } = this . state ;
const statsState = stats ;
2018-05-03 03:03:36 +08:00
let promise ;
2018-04-28 04:53:02 +08:00
try {
promise = peer . getStats ( mediaStreamTrack ) ;
} catch ( e ) {
promise = Promise . reject ( e ) ;
}
2018-05-26 03:24:41 +08:00
promise . then ( ( results ) => {
2018-05-03 03:03:36 +08:00
let videoInOrOutbound = { } ;
2018-06-15 03:20:14 +08:00
results . forEach ( ( res ) => {
if ( res . type === 'ssrc' || res . type === 'inbound-rtp' || res . type === 'outbound-rtp' ) {
2018-12-18 06:19:26 +08:00
res . packetsSent = parseInt ( res . packetsSent , 10 ) ;
res . packetsLost = parseInt ( res . packetsLost , 10 ) || 0 ;
res . packetsReceived = parseInt ( res . packetsReceived , 10 ) ;
2018-05-03 02:42:36 +08:00
2019-04-24 04:23:32 +08:00
if ( ( Number . isNaN ( res . packetsSent ) && res . packetsReceived === 0 )
2018-06-15 03:20:14 +08:00
|| ( res . type === 'outbound-rtp' && res . isRemote ) ) {
2018-05-03 02:42:36 +08:00
return ; // Discard local video receiving
}
if ( res . googFrameWidthReceived ) {
2018-12-18 06:19:26 +08:00
res . width = parseInt ( res . googFrameWidthReceived , 10 ) ;
res . height = parseInt ( res . googFrameHeightReceived , 10 ) ;
2018-05-03 02:42:36 +08:00
} else if ( res . googFrameWidthSent ) {
2018-12-18 06:19:26 +08:00
res . width = parseInt ( res . googFrameWidthSent , 10 ) ;
res . height = parseInt ( res . googFrameHeightSent , 10 ) ;
2018-04-28 04:53:02 +08:00
}
2018-05-03 02:42:36 +08:00
// Extra fields available on Chrome
if ( res . googCodecName ) res . codec = res . googCodecName ;
if ( res . googDecodeMs ) res . decodeDelay = res . googDecodeMs ;
if ( res . googEncodeUsagePercent ) res . encodeUsagePercent = res . googEncodeUsagePercent ;
if ( res . googRtt ) res . rtt = res . googRtt ;
if ( res . googCurrentDelayMs ) res . currentDelay = res . googCurrentDelayMs ;
videoInOrOutbound = res ;
2018-04-28 04:53:02 +08:00
}
} ) ;
2018-05-03 03:03:36 +08:00
const videoStats = {
2018-04-28 04:53:02 +08:00
timestamp : videoInOrOutbound . timestamp ,
bytesReceived : videoInOrOutbound . bytesReceived ,
bytesSent : videoInOrOutbound . bytesSent ,
packetsReceived : videoInOrOutbound . packetsReceived ,
packetsLost : videoInOrOutbound . packetsLost ,
packetsSent : videoInOrOutbound . packetsSent ,
decodeDelay : videoInOrOutbound . decodeDelay ,
codec : videoInOrOutbound . codec ,
height : videoInOrOutbound . height ,
width : videoInOrOutbound . width ,
encodeUsagePercent : videoInOrOutbound . encodeUsagePercent ,
rtt : videoInOrOutbound . rtt ,
currentDelay : videoInOrOutbound . currentDelay ,
2019-04-19 05:15:48 +08:00
pliCount : videoInOrOutbound . pliCount ,
2018-04-28 04:53:02 +08:00
} ;
2018-06-15 03:20:14 +08:00
const videoStatsArray = statsState ;
2018-04-28 04:53:02 +08:00
videoStatsArray . push ( videoStats ) ;
2018-06-15 03:20:14 +08:00
while ( videoStatsArray . length > 5 ) { // maximum interval to consider
2018-04-28 04:53:02 +08:00
videoStatsArray . shift ( ) ;
}
2019-05-24 02:25:31 +08:00
if ( ! monitoring ) {
this . setState ( { stats : videoStatsArray } ) ;
}
2018-04-28 04:53:02 +08:00
2018-05-03 03:03:36 +08:00
const firstVideoStats = videoStatsArray [ 0 ] ;
const lastVideoStats = videoStatsArray [ videoStatsArray . length - 1 ] ;
2018-04-28 04:53:02 +08:00
2018-05-03 03:03:36 +08:00
const videoIntervalPacketsLost = lastVideoStats . packetsLost - firstVideoStats . packetsLost ;
2019-04-24 04:23:32 +08:00
const videoIntervalPacketsReceived = lastVideoStats
. packetsReceived - firstVideoStats . packetsReceived ;
2018-05-03 03:03:36 +08:00
const videoIntervalPacketsSent = lastVideoStats . packetsSent - firstVideoStats . packetsSent ;
2019-04-24 04:23:32 +08:00
const videoIntervalBytesReceived = lastVideoStats
. bytesReceived - firstVideoStats . bytesReceived ;
2018-05-03 03:03:36 +08:00
const videoIntervalBytesSent = lastVideoStats . bytesSent - firstVideoStats . bytesSent ;
2018-04-28 04:53:02 +08:00
2018-05-03 03:03:36 +08:00
const videoReceivedInterval = lastVideoStats . timestamp - firstVideoStats . timestamp ;
const videoSentInterval = lastVideoStats . timestamp - firstVideoStats . timestamp ;
2018-04-28 04:53:02 +08:00
2018-06-15 03:20:14 +08:00
const videoKbitsReceivedPerSecond = ( videoIntervalBytesReceived * 8 ) / videoReceivedInterval ;
const videoKbitsSentPerSecond = ( videoIntervalBytesSent * 8 ) / videoSentInterval ;
2018-04-28 04:53:02 +08:00
2018-12-18 06:19:26 +08:00
let videoLostPercentage ;
let videoLostRecentPercentage ;
let videoBitrate ;
2018-04-28 04:53:02 +08:00
if ( videoStats . packetsReceived > 0 ) { // Remote video
2019-06-08 04:45:54 +08:00
videoLostPercentage = ( ( videoStats . packetsLost / (
( videoStats . packetsLost + videoStats . packetsReceived ) * 100
) ) || 0 ) . toFixed ( 1 ) ;
2018-05-03 03:03:36 +08:00
videoBitrate = Math . floor ( videoKbitsReceivedPerSecond || 0 ) ;
2019-04-24 04:23:32 +08:00
videoLostRecentPercentage = ( ( videoIntervalPacketsLost / ( ( videoIntervalPacketsLost
+ videoIntervalPacketsReceived ) * 100 ) ) || 0 ) . toFixed ( 1 ) ;
2018-04-28 04:53:02 +08:00
} else {
2019-04-24 04:23:32 +08:00
videoLostPercentage = ( ( ( videoStats . packetsLost / ( videoStats . packetsLost
+ videoStats . packetsSent ) ) * 100 ) || 0 ) . toFixed ( 1 ) ;
2018-05-03 03:03:36 +08:00
videoBitrate = Math . floor ( videoKbitsSentPerSecond || 0 ) ;
2019-04-24 04:23:32 +08:00
videoLostRecentPercentage = ( ( videoIntervalPacketsLost / ( ( videoIntervalPacketsLost
+ videoIntervalPacketsSent ) * 100 ) ) || 0 ) . toFixed ( 1 ) ;
2018-04-28 04:53:02 +08:00
}
2018-05-03 03:03:36 +08:00
const result = {
2018-04-28 04:53:02 +08:00
video : {
bytesReceived : videoStats . bytesReceived ,
bytesSent : videoStats . bytesSent ,
packetsLost : videoStats . packetsLost ,
packetsReceived : videoStats . packetsReceived ,
packetsSent : videoStats . packetsSent ,
bitrate : videoBitrate ,
lostPercentage : videoLostPercentage ,
lostRecentPercentage : videoLostRecentPercentage ,
height : videoStats . height ,
width : videoStats . width ,
codec : videoStats . codec ,
decodeDelay : videoStats . decodeDelay ,
encodeUsagePercent : videoStats . encodeUsagePercent ,
rtt : videoStats . rtt ,
currentDelay : videoStats . currentDelay ,
2019-04-19 05:15:48 +08:00
pliCount : videoStats . pliCount ,
2018-06-15 03:20:14 +08:00
} ,
2018-04-28 04:53:02 +08:00
} ;
callback ( result ) ;
2018-06-15 03:20:14 +08:00
} , ( exception ) => {
2019-04-24 02:26:28 +08:00
this . logger ( 'error' , ` customGetStats() Promise rejected: ${ exception . message } ` , 'video_provider_get_stats_exception' ) ;
2018-04-28 04:53:02 +08:00
callback ( null ) ;
} ) ;
}
2018-06-15 03:20:14 +08:00
monitorTrackStart ( peer , track , local , callback ) {
2018-04-28 04:53:02 +08:00
const that = this ;
2019-04-24 02:26:28 +08:00
this . logger ( 'info' , 'Starting stats monitoring on' , 'video_provider_monitor_track_start' , { cameraId : track . id } ) ;
2018-04-28 04:53:02 +08:00
const getStatsInterval = 2000 ;
const callGetStats = ( ) => {
that . customGetStats (
peer ,
track ,
2018-06-15 03:20:14 +08:00
( results ) => {
if ( results == null || peer . signalingState === 'closed' ) {
2018-04-28 04:53:02 +08:00
that . monitorTrackStop ( track . id ) ;
} else {
callback ( results ) ;
}
} ,
2018-06-15 03:20:14 +08:00
getStatsInterval ,
) ;
2018-04-28 04:53:02 +08:00
} ;
if ( ! this . monitoredTracks [ track . id ] ) {
callGetStats ( ) ;
this . monitoredTracks [ track . id ] = setInterval (
callGetStats ,
getStatsInterval ,
) ;
} else {
2019-04-24 02:26:28 +08:00
this . logger ( 'info' , 'Already monitoring this track' , 'video_provider_already_monitoring_track' ) ;
2018-04-28 04:53:02 +08:00
}
}
2018-06-15 03:20:14 +08:00
monitorTrackStop ( trackId ) {
2018-04-28 04:53:02 +08:00
if ( this . monitoredTracks [ trackId ] ) {
clearInterval ( this . monitoredTracks [ trackId ] ) ;
delete this . monitoredTracks [ trackId ] ;
2019-04-24 02:26:28 +08:00
this . logger ( 'debug' , ` Track ${ trackId } removed ` , 'video_provider_stop_monitoring' ) ;
2018-04-28 04:53:02 +08:00
} else {
2019-04-24 02:26:28 +08:00
this . logger ( 'debug' , ` Track ${ trackId } is not monitored ` , 'video_provider_already_stopped_monitoring' ) ;
2018-04-28 04:53:02 +08:00
}
}
2018-04-27 06:16:30 +08:00
stopGettingStats ( id ) {
const peer = this . webRtcPeers [ id ] ;
2018-12-18 06:19:26 +08:00
const hasLocalStream = peer && peer . started === true
&& peer . peerConnection . getLocalStreams ( ) . length > 0 ;
const hasRemoteStream = peer && peer . started === true
&& peer . peerConnection . getRemoteStreams ( ) . length > 0 ;
2018-05-26 03:24:41 +08:00
if ( hasLocalStream ) {
this . monitorTrackStop ( peer . peerConnection . getLocalStreams ( ) [ 0 ] . getVideoTracks ( ) [ 0 ] . id ) ;
} else if ( hasRemoteStream ) {
this . monitorTrackStop ( peer . peerConnection . getRemoteStreams ( ) [ 0 ] . getVideoTracks ( ) [ 0 ] . id ) ;
2018-04-27 06:16:30 +08:00
}
}
2018-02-17 03:18:53 +08:00
handlePlayStop ( message ) {
2018-08-13 06:40:53 +08:00
const { cameraId } = message ;
2018-04-19 02:16:26 +08:00
2019-04-24 02:26:28 +08:00
this . logger ( 'info' , 'Handle play stop for camera' , 'video_provider_handle_play_stop' , { cameraId } ) ;
2018-08-21 02:08:10 +08:00
this . stopWebRTCPeer ( cameraId ) ;
2018-02-17 03:18:53 +08:00
}
handlePlayStart ( message ) {
2018-04-19 02:16:26 +08:00
const id = message . cameraId ;
const peer = this . webRtcPeers [ id ] ;
2018-04-14 03:16:06 +08:00
2018-07-12 00:32:19 +08:00
if ( peer ) {
2018-12-18 06:19:26 +08:00
const { userId } = this . props ;
2019-04-24 02:26:28 +08:00
this . logger ( 'info' , 'Handle play start for camera' , 'video_provider_handle_play_start' , { cameraId : id } ) ;
2018-05-23 04:55:24 +08:00
2018-07-12 00:32:19 +08:00
// Clear camera shared timeout when camera succesfully starts
clearTimeout ( this . restartTimeout [ id ] ) ;
delete this . restartTimeout [ id ] ;
delete this . restartTimer [ id ] ;
2018-02-17 03:18:53 +08:00
2018-07-12 00:32:19 +08:00
peer . started = true ;
2018-04-14 03:16:06 +08:00
2018-07-12 00:32:19 +08:00
if ( ! peer . attached ) {
this . attachVideoStream ( id ) ;
}
2018-05-23 04:55:24 +08:00
2018-12-18 06:19:26 +08:00
if ( id === userId ) {
2018-07-12 00:32:19 +08:00
VideoService . sendUserShareWebcam ( id ) ;
VideoService . joinedVideo ( ) ;
}
} else {
2019-04-24 02:26:28 +08:00
this . logger ( 'warn' , '[playStart] Message arrived after the peer was already thrown out, discarding it...' , 'video_provider_play_start_discarding' ) ;
2018-02-17 03:18:53 +08:00
}
}
2018-07-21 03:34:50 +08:00
handleSFUError ( message ) {
2018-02-17 03:18:53 +08:00
const { intl } = this . props ;
2018-05-17 21:23:18 +08:00
const { userId } = this . props ;
2018-07-21 03:34:50 +08:00
const { code , reason } = message ;
2019-04-24 03:57:31 +08:00
this . logger ( 'error' , 'Received error from SFU:' , 'video_provider_sfu_error' , {
code , reason , streamId : message . streamId , userId ,
} ) ;
2018-07-21 03:34:50 +08:00
if ( message . streamId === userId ) {
2018-02-17 03:18:53 +08:00
this . unshareWebcam ( ) ;
2019-04-24 04:23:32 +08:00
VideoProvider . notifyError ( intl . formatMessage ( intlSFUErrors [ code ]
2018-12-18 01:45:57 +08:00
|| intlSFUErrors [ 2200 ] ) ) ;
2018-02-17 03:18:53 +08:00
} else {
2018-04-19 02:16:26 +08:00
this . stopWebRTCPeer ( message . cameraId ) ;
2018-02-17 03:18:53 +08:00
}
2019-04-24 02:26:28 +08:00
this . logger ( 'error' , ` Handle error ---------------------> ${ message . message } ` , 'video_provider_handle_sfu_error' , { message } ) ;
2018-02-17 03:18:53 +08:00
}
shareWebcam ( ) {
2018-03-21 15:27:06 +08:00
if ( this . connectedToMediaServer ( ) ) {
2019-04-24 02:26:28 +08:00
this . logger ( 'info' , 'Sharing webcam' , 'video_provider_share_webcam' ) ;
2018-07-10 05:29:27 +08:00
this . sharedWebcam = true ;
2018-03-21 15:27:06 +08:00
VideoService . joiningVideo ( ) ;
}
2018-02-17 03:18:53 +08:00
}
unshareWebcam ( ) {
2018-12-18 06:19:26 +08:00
const { userId } = this . props ;
2019-04-24 02:26:28 +08:00
this . logger ( 'info' , 'Unsharing webcam' , 'video_provider_unshare_webcam' ) ;
2018-02-17 05:11:59 +08:00
2018-12-18 06:19:26 +08:00
VideoService . sendUserUnshareWebcam ( userId ) ;
2018-04-19 02:16:26 +08:00
VideoService . exitedVideo ( ) ;
2018-07-10 05:29:27 +08:00
this . sharedWebcam = false ;
2018-02-17 03:18:53 +08:00
}
render ( ) {
2018-12-18 06:19:26 +08:00
const { socketOpen } = this . state ;
if ( ! socketOpen ) return null ;
2018-04-12 02:50:14 +08:00
2019-06-08 04:45:54 +08:00
const {
users ,
enableVideoStats ,
cursor ,
swapLayout ,
mediaHeight ,
} = this . props ;
2018-02-17 03:18:53 +08:00
return (
2018-04-12 02:50:14 +08:00
< VideoList
2019-04-24 04:23:32 +08:00
cursor = { cursor }
2019-06-08 04:45:54 +08:00
swapLayout = { swapLayout }
mediaHeight = { mediaHeight }
2018-12-18 06:19:26 +08:00
users = { users }
onMount = { this . createVideoTag }
getStats = { this . getStats }
stopGettingStats = { this . stopGettingStats }
enableVideoStats = { enableVideoStats }
2018-03-20 01:52:39 +08:00
/ >
2018-02-17 03:18:53 +08:00
) ;
}
}
2018-03-12 23:29:51 +08:00
export default injectIntl ( VideoProvider ) ;