From 55feaa30af5f58130c976a4f7ea33c2183adeeb1 Mon Sep 17 00:00:00 2001 From: Lucas Fialho Zawacki Date: Thu, 25 Jan 2018 03:40:16 +0000 Subject: [PATCH 1/6] Timeout cameras that have not started --- .../ui/components/video-dock/component.jsx | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx b/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx index e22a8489a7..d7751b6d93 100644 --- a/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx @@ -32,6 +32,7 @@ const intlMessages = defineMessages({ const RECONNECT_WAIT_TIME = 5000; const INITIAL_SHARE_WAIT_TIME = 2000; +const CAMERA_SHARE_FAILED_WAIT_TIME = 15000; class VideoElement extends Component { constructor(props) { @@ -57,8 +58,7 @@ class VideoDock extends Component { this.webRtcPeers = {}; this.reconnectWebcam = false; this.reconnectList = []; - this.sharedCameraTimeout = null; - this.subscribedCamerasTimeouts = []; + this.cameraTimeouts = {}; this.state = { videos: {}, @@ -186,10 +186,6 @@ class VideoDock extends Component { this.startResponse(parsedMessage); break; - case 'error': - this.handleError(parsedMessage); - break; - case 'playStart': this.handlePlayStart(parsedMessage); break; @@ -218,14 +214,31 @@ class VideoDock extends Component { log('error', ' [ICE] Message arrived after the peer was already thrown out, discarding it...'); } break; + + case 'error': + default: + this.handleError(parsedMessage); + break; } }; start(id, shareWebcam) { const that = this; + const { intl } = this.props; console.log(`Starting video call for video: ${id} with ${shareWebcam}`); + this.cameraTimeouts[id] = setTimeout(() => { + log('error', `Camera share has not suceeded in ${CAMERA_SHARE_FAILED_WAIT_TIME}`); + if (that.myId == id) { + this.notifyError(intl.formatMessage(intlMessages.sharingError)); + that.stop(id); + } else { + that.stop(id); + that.start(id, shareWebcam); + } + }, CAMERA_SHARE_FAILED_WAIT_TIME); + if (shareWebcam) { VideoService.joiningVideo(); this.setState({sharedWebcam: true}); @@ -389,6 +402,10 @@ class VideoDock extends Component { destroyWebRTCPeer(id) { const webRtcPeer = this.webRtcPeers[id]; + // Clear the shared camera fail timeout when destroying + clearTimeout(this.cameraTimeouts[id]); + this.cameraTimeouts[id] = null; + if (webRtcPeer) { log('info', 'Stopping WebRTC peer'); @@ -439,12 +456,12 @@ class VideoDock extends Component { if (error) { return log('error', error); } - }); - if (message.cameraId == this.props.userId) { - log('info', "camera id sendusershare ", id); - VideoService.sendUserShareWebcam(id); - } + if (message.cameraId == this.props.userId) { + log('info', "camera id sendusershare ", id); + VideoService.sendUserShareWebcam(id); + } + }); } sendMessage(message) { @@ -488,6 +505,10 @@ class VideoDock extends Component { handlePlayStart(message) { log('info', 'Handle play start <==================='); + // Clear camera shared timeout when camera succesfully starts + clearTimeout(this.cameraTimeouts[message.cameraId]); + this.cameraTimeouts[message.cameraId] = null; + if (message.cameraId == this.props.userId) { VideoService.joinedVideo(); } @@ -495,10 +516,10 @@ class VideoDock extends Component { handleError(message) { const { intl, userId } = this.props; - this.notifyError(intl.formatMessage(intlMessages.sharingError)); if (message.cameraId == userId) { this.unshareWebcam(); + this.notifyError(intl.formatMessage(intlMessages.sharingError)); } else { this.stop(message.cameraId); } From a1e7833fa390abd496d6d740043c7d46e61d675a Mon Sep 17 00:00:00 2001 From: Lucas Fialho Zawacki Date: Thu, 25 Jan 2018 03:44:08 +0000 Subject: [PATCH 2/6] Better video reconnect when network is offline --- .../imports/ui/components/video-dock/component.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx b/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx index d7751b6d93..5ca5bed0c1 100644 --- a/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx @@ -130,7 +130,7 @@ class VideoDock extends Component { this.ws.addEventListener('close', this.onWsClose); window.addEventListener('online', this.ws.open.bind(this.ws)); - window.addEventListener('offline', this.ws.close.bind(this.ws)); + window.addEventListener('offline', this.onWsClose); } componentWillUnmount () { @@ -144,8 +144,8 @@ class VideoDock extends Component { this.ws.removeEventListener('close', this.onWsClose); // Close websocket connection to prevent multiple reconnects from happening - window.removeEventListener('online', this.ws.open); - window.removeEventListener('offline', this.ws.close); + window.removeEventListener('online', this.ws.open.bind(this.ws)); + window.removeEventListener('offline', this.onWsClose); this.ws.close(); } From 8be4fd6ef4f18e8c02544369ee84c1ad17b224e1 Mon Sep 17 00:00:00 2001 From: Lucas Fialho Zawacki Date: Thu, 25 Jan 2018 03:48:49 +0000 Subject: [PATCH 3/6] Better way to handle shouldComponentUpdate callback on HTML5 video This commit matches userIds on the old/new userlist when the react component updates and does not assume the lists are ordered. It also prevents things from getting funky when a user leaves or joins the list --- .../ui/components/video-dock/component.jsx | 68 ++++++++++++------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx b/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx index 5ca5bed0c1..8802ba1b23 100644 --- a/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx @@ -566,38 +566,60 @@ class VideoDock extends Component { } shouldComponentUpdate(nextProps, nextState) { - const { users, userId } = this.props; + const { userId } = this.props; + const currentUsers = this.props.users || {}; const nextUsers = nextProps.users; - if (users) { - let suc = false; + let users = {}; + let present = {}; - for (let i = 0; i < users.length; i++) { - if (users && users[i] && - nextUsers && nextUsers[i]) { - if (users[i].has_stream !== nextUsers[i].has_stream) { - console.log(`User ${nextUsers[i].has_stream ? '' : 'un'}shared webcam ${users[i].userId}`); + if (!currentUsers) + return false; - if (nextUsers[i].has_stream) { - if (userId !== users[i].userId) { - this.start(users[i].userId, false); - } - } else { - this.stop(users[i].userId); - } + // Map user objectos to an object in the form {userId: has_stream} + currentUsers.forEach((user) => { + users[user.userId] = user.has_stream; + }); - if (!nextUsers[i].has_stream) { - this.destroyVideoTag(users[i].userId); - } + // Keep instances where the flag has changed or next user adds it + nextUsers.forEach((user) => { + let id = user.userId; + // The case when a user exists and stream status has not changed + if (users[id] === user.has_stream) { + delete users[id]; + } else { + // Case when a user has been added to the list + users[id] = user.has_stream; + } - suc = suc || true; - } + // Mark the ids which are present in nextUsers + present[id] = true; + }); + + console.log(users); + const userIds = Object.keys(users); + + for (let i = 0; i < userIds.length; i++) { + let id = userIds[i]; + + // + if (!present[id]) { + this.stop(id); + continue; + } + + console.log(`User ${users[id] ? '' : 'un'}shared webcam ${id}`); + + if (users[id]) { + if (userId !== id) { + this.start(id, false); } } - return true; + else { + this.stop(id); + } } - - return false; + return true; } } From c36f322fffcf3662927c9a8291e391eaaa1560b9 Mon Sep 17 00:00:00 2001 From: Lucas Fialho Zawacki Date: Wed, 31 Jan 2018 19:35:15 +0000 Subject: [PATCH 4/6] Better handling of shared video timeout in html5 --- .../imports/ui/components/video-dock/component.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx b/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx index 8802ba1b23..6c3e63f961 100644 --- a/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx @@ -32,7 +32,7 @@ const intlMessages = defineMessages({ const RECONNECT_WAIT_TIME = 5000; const INITIAL_SHARE_WAIT_TIME = 2000; -const CAMERA_SHARE_FAILED_WAIT_TIME = 15000; +const CAMERA_SHARE_FAILED_WAIT_TIME = 10000; class VideoElement extends Component { constructor(props) { @@ -231,8 +231,8 @@ class VideoDock extends Component { this.cameraTimeouts[id] = setTimeout(() => { log('error', `Camera share has not suceeded in ${CAMERA_SHARE_FAILED_WAIT_TIME}`); if (that.myId == id) { - this.notifyError(intl.formatMessage(intlMessages.sharingError)); - that.stop(id); + that.notifyError(intl.formatMessage(intlMessages.sharingError)); + that.unshareWebcam(); } else { that.stop(id); that.start(id, shareWebcam); From 4dce4676cb2342c47a4d7a50852f59708055bc8f Mon Sep 17 00:00:00 2001 From: Lucas Fialho Zawacki Date: Wed, 31 Jan 2018 20:12:08 +0000 Subject: [PATCH 5/6] Wait a while before closing streams if media stops flowing on bbb-webrtc-sfu --- labs/bbb-webrtc-sfu/lib/video/video.js | 32 ++++++++++++++++++++------ 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/labs/bbb-webrtc-sfu/lib/video/video.js b/labs/bbb-webrtc-sfu/lib/video/video.js index c7ea9f8e43..ec2802a213 100644 --- a/labs/bbb-webrtc-sfu/lib/video/video.js +++ b/labs/bbb-webrtc-sfu/lib/video/video.js @@ -23,6 +23,7 @@ module.exports = class Video { this.iceQueue = null; this.candidatesQueue = []; + this.notFlowingTimeout = null; } onIceCandidate (_candidate) { @@ -80,15 +81,26 @@ module.exports = class Video { Logger.info('[video] ' + msEvent.type + '[' + msEvent.state + ']' + ' for media session', event.id, "for video", this.id); if (msEvent.state === 'NOT_FLOWING') { - this.bbbGW.publish(JSON.stringify({ - connectionId: this.sessionId, - type: 'video', - role: this.role, - id : 'playStop', - cameraId: this.id, - }), C.FROM_VIDEO); + Logger.warn("Setting up a timeout for " + this.sessionId + " camera " + this.id); + if (!this.notFlowingTimeout) { + this.notFlowingTimeout = setTimeout(() => { + this.bbbGW.publish(JSON.stringify({ + connectionId: this.sessionId, + type: 'video', + role: this.role, + id : 'playStop', + cameraId: this.id, + }), C.FROM_VIDEO); + }, config.get('mediaFlowTimeoutDuration')); + } } else if (msEvent.state === 'FLOWING') { + if (this.notFlowingTimeout) { + Logger.warn("Received a media flow before stopping " + this.sessionId + " camera " + this.id); + clearTimeout(this.notFlowingTimeout); + this.notFlowingTimeout = null; + } + this.bbbGW.publish(JSON.stringify({ connectionId: this.sessionId, type: 'video', @@ -152,6 +164,12 @@ module.exports = class Video { if (this.shared) { sharedWebcams[this.id] = null; } + + if (this.notFlowingTimeout) { + clearTimeout(this.notFlowingTimeout); + this.notFlowingTimeout = null; + } + this._candidatesQueue = null; Promise.resolve(); } From 829930f13125a0948a93229f10e7986ef103d277 Mon Sep 17 00:00:00 2001 From: Lucas Fialho Zawacki Date: Wed, 31 Jan 2018 20:55:56 +0000 Subject: [PATCH 6/6] Remove logging and some comments to shouldUpdateComponent --- .../imports/ui/components/video-dock/component.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx b/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx index 6c3e63f961..72919cf771 100644 --- a/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx @@ -596,13 +596,12 @@ class VideoDock extends Component { present[id] = true; }); - console.log(users); const userIds = Object.keys(users); for (let i = 0; i < userIds.length; i++) { let id = userIds[i]; - // + // If a userId is not present in nextUsers let's stop it if (!present[id]) { this.stop(id); continue; @@ -610,6 +609,8 @@ class VideoDock extends Component { console.log(`User ${users[id] ? '' : 'un'}shared webcam ${id}`); + // If a user stream is true, changed and was shared by other + // user we'll start it. If it is false and changed we stop it if (users[id]) { if (userId !== id) { this.start(id, false);