diff --git a/bigbluebutton-html5/client/compatibility/kurento-extension.js b/bigbluebutton-html5/client/compatibility/kurento-extension.js index cdfc720654b3def6fc16fce9e557cc07b3ed7e32..4ca52d78a9b90bdf298d19a5b835d763f922e72e 100644 --- a/bigbluebutton-html5/client/compatibility/kurento-extension.js +++ b/bigbluebutton-html5/client/compatibility/kurento-extension.js @@ -13,7 +13,6 @@ Kurento = function ( onSuccess, options = {}, ) { - this.ws = null; this.video = null; this.screen = null; @@ -30,10 +29,10 @@ Kurento = function ( Object.assign(this, options); - this.SEND_ROLE = "send"; - this.RECV_ROLE = "recv"; - this.SFU_APP = "screenshare"; - this.ON_ICE_CANDIDATE_MSG = "iceCandidate"; + this.SEND_ROLE = 'send'; + this.RECV_ROLE = 'recv'; + this.SFU_APP = 'screenshare'; + this.ON_ICE_CANDIDATE_MSG = 'iceCandidate'; this.PING_INTERVAL = 15000; window.Logger = this.logger || console; @@ -45,11 +44,11 @@ Kurento = function ( } if (this.chromeScreenshareSources == null) { - this.chromeScreenshareSources = ["screen", "window"]; + this.chromeScreenshareSources = ['screen', 'window']; } if (this.firefoxScreenshareSource == null) { - this.firefoxScreenshareSource = "window"; + this.firefoxScreenshareSource = 'window'; } // Limiting max resolution to WQXGA @@ -78,7 +77,7 @@ Kurento = function ( if (onSuccess != null) { this.onSuccess = Kurento.normalizeCallback(onSuccess); } else { - var _this = this; + const _this = this; this.onSuccess = function () { _this.logSuccess('Default success handler'); }; @@ -93,7 +92,6 @@ this.KurentoManager = function () { KurentoManager.prototype.exitScreenShare = function () { if (typeof this.kurentoScreenshare !== 'undefined' && this.kurentoScreenshare) { - if (this.kurentoScreenshare.logger !== null) { this.kurentoScreenshare.logger.info(' [exitScreenShare] Exiting screensharing'); } @@ -119,6 +117,10 @@ KurentoManager.prototype.exitScreenShare = function () { KurentoManager.prototype.exitVideo = function () { if (typeof this.kurentoVideo !== 'undefined' && this.kurentoVideo) { + if(this.kurentoVideo.webRtcPeer) { + this.kurentoVideo.webRtcPeer.peerConnection.oniceconnectionstatechange = null; + } + if (this.kurentoVideo.logger !== null) { this.kurentoVideo.logger.info(' [exitScreenShare] Exiting screensharing viewing'); } @@ -139,7 +141,6 @@ KurentoManager.prototype.exitVideo = function () { KurentoManager.prototype.exitAudio = function () { if (typeof this.kurentoAudio !== 'undefined' && this.kurentoAudio) { - if (this.kurentoAudio.logger !== null) { this.kurentoAudio.logger.info(' [exitAudio] Exiting listen only audio'); } @@ -177,15 +178,15 @@ KurentoManager.prototype.joinWatchVideo = function (tag) { KurentoManager.prototype.getFirefoxScreenshareSource = function () { return this.kurentoScreenshare.firefoxScreenshareSource; -} +}; KurentoManager.prototype.getChromeScreenshareSources = function () { return this.kurentoScreenshare.chromeScreenshareSources; -} +}; KurentoManager.prototype.getChromeExtensionKey = function () { return this.kurentoScreenshare.chromeExtension; -} +}; Kurento.prototype.setScreensharing = function (tag) { @@ -201,13 +202,13 @@ Kurento.prototype.create = function (tag) { Kurento.prototype.downscaleResolution = function (oldWidth, oldHeight) { const factorWidth = this.vid_max_width / oldWidth; const factorHeight = this.vid_max_height / oldHeight; - let width, height; + let width, + height; if (factorWidth < factorHeight) { width = Math.trunc(oldWidth * factorWidth); height = Math.trunc(oldHeight * factorWidth); - } - else { + } else { width = Math.trunc(oldWidth * factorHeight); height = Math.trunc(oldHeight * factorHeight); } @@ -269,13 +270,12 @@ Kurento.prototype.setRenderTag = function (tag) { Kurento.prototype.startResponse = function (message) { if (message.response !== 'accepted') { const errorMsg = message.message ? message.message : 'Unknow error'; - this.logger.warn("Call not accepted for the following reason:", {error: errorMsg}); + this.logger.warn('Call not accepted for the following reason:', { error: errorMsg }); switch (message.type) { case 'screenshare': if (message.role === this.SEND_ROLE) { kurentoManager.exitScreenShare(); - } - else if (message.role === this.RECV_ROLE) { + } else if (message.role === this.RECV_ROLE) { kurentoManager.exitVideo(); } break; @@ -284,7 +284,7 @@ Kurento.prototype.startResponse = function (message) { break; } } else { - this.logger.debug(`Procedure for ${message.type} was accepted with SDP =>`, {sdpAnswer: message.sdpAnswer}); + this.logger.debug(`Procedure for ${message.type} was accepted with SDP =>`, { sdpAnswer: message.sdpAnswer }); this.webRtcPeer.processAnswer(message.sdpAnswer); } }; @@ -310,7 +310,7 @@ Kurento.prototype.onOfferPresenter = function (error, offerSdp) { vw: this.width, }; - this.logger.info("onOfferPresenter sending to screenshare server => ", { sdpOffer: message }); + this.logger.info('onOfferPresenter sending to screenshare server => ', { sdpOffer: message }); this.sendMessage(message); }; @@ -335,7 +335,7 @@ Kurento.prototype.startScreensharing = function () { sendSource: 'desktop', }; - this.logger.info(" Peer options =>", options); + this.logger.info(' Peer options =>', options); let resolution; this.logger.debug(`Screenshare screen dimensions are ${this.width} x ${this.height}`); @@ -343,23 +343,25 @@ Kurento.prototype.startScreensharing = function () { resolution = this.downscaleResolution(this.width, this.height); this.width = resolution.width; this.height = resolution.height; - this.logger.debug("Screenshare track dimensions have been resized to", this.width, "x", this.height); + this.logger.debug('Screenshare track dimensions have been resized to', this.width, 'x', this.height); } this.addIceServers(this.iceServers, options); this.webRtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerSendonly(options, (error) => { if (error) { - this.logger.error("WebRtcPeerSendonly constructor error:", {error}); + this.logger.error('WebRtcPeerSendonly constructor error:', { error }); this.onFail(error); return kurentoManager.exitScreenShare(); } this.webRtcPeer.generateOffer(this.onOfferPresenter.bind(this)); - this.logger.info("Generated peer offer w/ options:", {options: options}); + this.logger.info('Generated peer offer w/ options:', { options }); const localStream = this.webRtcPeer.peerConnection.getLocalStreams()[0]; + const _this = this; localStream.getVideoTracks()[0].onended = function () { + _this.webRtcPeer.peerConnection.oniceconnectionstatechange = null; return kurentoManager.exitScreenShare(); }; @@ -367,15 +369,24 @@ Kurento.prototype.startScreensharing = function () { return kurentoManager.exitScreenShare(); }; }); + this.webRtcPeer.peerConnection.oniceconnectionstatechange = () => { + if (this.webRtcPeer) { + const connectionState = this.webRtcPeer.peerConnection.iceConnectionState; + if (connectionState === 'failed' || connectionState === 'closed') { + this.webRtcPeer.peerConnection.oniceconnectionstatechange = null; + this.onFail('ICE connection failed'); + } + } + }; }; Kurento.prototype.onIceCandidate = function (candidate, role) { const self = this; - this.logger.info("Local candidate:", {candidate}); + this.logger.info('Local candidate:', { candidate }); const message = { id: this.ON_ICE_CANDIDATE_MSG, - role: role, + role, type: this.SFU_APP, voiceBridge: self.voiceBridge, candidate, @@ -398,13 +409,13 @@ Kurento.prototype.viewer = function () { if (!this.webRtcPeer) { const options = { mediaConstraints: { - audio: false + audio: false, }, remoteVideo: document.getElementById(this.renderTag), onicecandidate: (candidate) => { this.onIceCandidate(candidate, this.RECV_ROLE); - } - } + }, + }; this.addIceServers(this.iceServers, options); @@ -415,6 +426,15 @@ Kurento.prototype.viewer = function () { this.generateOffer(self.onOfferViewer.bind(self)); }); + self.webRtcPeer.peerConnection.oniceconnectionstatechange = () => { + if (this.webRtcPeer) { + const connectionState = this.webRtcPeer.peerConnection.iceConnectionState; + if (connectionState === 'failed' || connectionState === 'closed') { + this.webRtcPeer.peerConnection.oniceconnectionstatechange = null; + this.onFail('ICE connection failed'); + } + } + }; } }; @@ -434,15 +454,15 @@ Kurento.prototype.onOfferViewer = function (error, offerSdp) { sdpOffer: offerSdp, }; - this.logger.info("onOfferViewer sending to screenshare server: ", {sdpOffer: message.sdpOffer}); + this.logger.info('onOfferViewer sending to screenshare server: ', { sdpOffer: message.sdpOffer }); this.sendMessage(message); }; KurentoManager.prototype.joinAudio = function (tag) { this.exitAudio(); - var obj = Object.create(Kurento.prototype); + const obj = Object.create(Kurento.prototype); Kurento.apply(obj, arguments); - this.kurentoAudio= obj; + this.kurentoAudio = obj; this.kurentoAudio.setAudio(tag); }; @@ -461,15 +481,15 @@ Kurento.prototype.listenOnly = function () { remoteVideo, onicecandidate : this.onListenOnlyIceCandidate.bind(this), mediaConstraints: { - audio:true, - video:false - } - } + audio: true, + video: false, + }, + }; this.addIceServers(this.iceServers, options); - self.webRtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly(options, function(error) { - if(error) { + self.webRtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly(options, function (error) { + if (error) { return self.onFail(PEER_ERROR); } @@ -479,39 +499,39 @@ Kurento.prototype.listenOnly = function () { }; Kurento.prototype.onListenOnlyIceCandidate = function (candidate) { - let self = this; - this.logger.debug("[onListenOnlyIceCandidate]", {candidate}); + const self = this; + this.logger.debug('[onListenOnlyIceCandidate]', { candidate }); - var message = { - id : this.ON_ICE_CANDIDATE_MSG, + const message = { + id: this.ON_ICE_CANDIDATE_MSG, type: 'audio', role: 'viewer', voiceBridge: self.voiceBridge, - candidate : candidate, - } + candidate, + }; this.sendMessage(message); }; Kurento.prototype.onOfferListenOnly = function (error, offerSdp) { - let self = this; - if(error) { - this.logger.error("[onOfferListenOnly]", error); + const self = this; + if (error) { + this.logger.error('[onOfferListenOnly]', error); return this.onFail(SDP_ERROR); } - let message = { - id : 'start', + const message = { + id: 'start', type: 'audio', role: 'viewer', voiceBridge: self.voiceBridge, caleeName: self.caleeName, - sdpOffer : offerSdp, + sdpOffer: offerSdp, userId: self.userId, userName: self.userName, - internalMeetingId: self.internalMeetingId + internalMeetingId: self.internalMeetingId, }; - this.logger.debug("[onOfferListenOnly]", {message}); + this.logger.debug('[onOfferListenOnly]', { message }); this.sendMessage(message); }; @@ -522,7 +542,7 @@ Kurento.prototype.pauseTrack = function (message) { if (track) { track.enabled = false; } -} +}; Kurento.prototype.resumeTrack = function (message) { const localStream = this.webRtcPeer.peerConnection.getLocalStreams()[0]; @@ -531,10 +551,10 @@ Kurento.prototype.resumeTrack = function (message) { if (track) { track.enabled = true; } -} +}; Kurento.prototype.addIceServers = function (iceServers, options) { - this.logger.debug("Adding iceServers", iceServers); + this.logger.debug('Adding iceServers', iceServers); if (iceServers && iceServers.length > 0) { options.configuration = {}; options.configuration.iceServers = iceServers; @@ -562,14 +582,14 @@ Kurento.prototype.dispose = function () { Kurento.prototype.ping = function () { const message = { - id: 'ping' + id: 'ping', }; this.sendMessage(message); -} +}; Kurento.prototype.sendMessage = function (message) { const jsonMessage = JSON.stringify(message); - this.logger.info("Sending message:", {message}); + this.logger.info('Sending message:', { message }); this.ws.send(jsonMessage); }; @@ -638,12 +658,12 @@ window.getScreenConstraints = function (sendSource, callback) { { googCpuOveruseDetection: true }, { googCpuOveruseEncodeUsage: true }, { googCpuUnderuseThreshold: 55 }, - { googCpuOveruseThreshold: 100}, + { googCpuOveruseThreshold: 100 }, { googPayloadPadding: true }, { googScreencastMinBitrate: 600 }, { googHighStartBitrate: true }, { googHighBitrate: true }, - { googVeryHighBitrate: true } + { googVeryHighBitrate: true }, ]; console.log('getScreenConstraints for Chrome returns => ', screenConstraints); @@ -662,7 +682,6 @@ window.getScreenConstraints = function (sendSource, callback) { if (isSafari) { // At this time (version 11.1), Safari doesn't support screenshare. document.dispatchEvent(new Event('safariScreenshareNotSupported')); - return; } }; @@ -739,4 +758,4 @@ window.checkChromeExtInstalled = function (callback, chromeExtensionId) { callback(true); } ); -}*/ +} */ diff --git a/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js b/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js index f266589972c617ca45545ae285d67ee46193a956..3c22b70b55a99d69445c888bd25c39d6a9c08a8a 100755 --- a/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js +++ b/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js @@ -3,6 +3,7 @@ import Auth from '/imports/ui/services/auth'; import BridgeService from './service'; import { fetchWebRTCMappedStunTurnServers } from '/imports/utils/fetchStunTurnServers'; import logger from '/imports/startup/client/logger'; +import { notify } from '/imports/ui/services/notification'; const SFU_CONFIG = Meteor.settings.public.kurento; const SFU_URL = SFU_CONFIG.wsUrl; @@ -14,6 +15,8 @@ const SCREENSHARE_VIDEO_TAG = 'screenshareVideo'; const CHROME_EXTENSION_KEY = CHROME_CUSTOM_EXTENSION_KEY === 'KEY' ? CHROME_DEFAULT_EXTENSION_KEY : CHROME_CUSTOM_EXTENSION_KEY; +const ICE_CONNECTION_FAILED = 'ICE connection failed'; + const getUserId = () => Auth.userID; const getMeetingId = () => Auth.meetingID; @@ -28,17 +31,17 @@ const logFunc = (type, message, options) => { const topic = options.topic || 'screenshare'; - logger[type]({obj: Object.assign(options, {userId, userName, topic})}, `[${topic}] ${message}`); + logger[type]({ obj: Object.assign(options, { userId, userName, topic }) }, `[${topic}] ${message}`); }; const modLogger = { - info: function (message, options = {}) { + info(message, options = {}) { logFunc('info', message, options); }, - error: function (message, options = {}) { + error(message, options = {}) { logFunc('error', message, options); }, - debug: function (message, options = {}) { + debug(message, options = {}) { logFunc('debug', message, options); }, warn: (message, options = {}) => { @@ -58,7 +61,7 @@ export default class KurentoScreenshareBridge { const options = { wsUrl: SFU_URL, iceServers, - logger: modLogger + logger: modLogger, }; window.kurentoWatchVideo( @@ -68,16 +71,16 @@ export default class KurentoScreenshareBridge { getMeetingId(), null, null, - options + options, ); - }; + } } kurentoExitVideo() { window.kurentoExitVideo(); } - async kurentoShareScreen() { + async kurentoShareScreen(onFail) { let iceServers = []; try { iceServers = await fetchWebRTCMappedStunTurnServers(getSessionToken()); @@ -92,15 +95,14 @@ export default class KurentoScreenshareBridge { iceServers, logger: modLogger, }; - window.kurentoShareScreen( SCREENSHARE_VIDEO_TAG, BridgeService.getConferenceBridge(), getUserId(), getMeetingId(), + onFail, null, - null, - options + options, ); } } diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx index b0a8150a32b7edf1dee47d9fba1ff83c47442f52..ce90a94c153c5783f6e67c2fb1f101597baed895 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx @@ -12,7 +12,7 @@ export default withTracker(() => ({ isUserModerator: Service.isUserModerator(), handleExitVideo: () => VideoService.exitVideo(), handleJoinVideo: () => VideoService.joinVideo(), - handleShareScreen: () => shareScreen(), + handleShareScreen: (onFail) => shareScreen(onFail), handleUnshareScreen: () => unshareScreen(), isVideoBroadcasting: isVideoBroadcasting(), recordSettingsList: Service.recordSettingsList(), diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/desktop-share/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/desktop-share/component.jsx index dc639ca2477380512d83621b0566bf4eeebafbb8..7329d4774cc7afead29ce78a3198c97533577952 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/desktop-share/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/desktop-share/component.jsx @@ -3,6 +3,8 @@ import PropTypes from 'prop-types'; import { defineMessages, injectIntl, intlShape } from 'react-intl'; import browser from 'browser-detect'; import Button from '/imports/ui/components/button/component'; +import logger from '/imports/startup/client/logger'; +import { notify } from '/imports/ui/services/notification'; import { styles } from '../styles'; const propTypes = { @@ -30,7 +32,10 @@ const intlMessages = defineMessages({ id: 'app.actionsBar.actionsDropdown.stopDesktopShareDesc', description: 'adds context to stop desktop share option', }, - + iceConnectionStateError: { + id: 'app.deskshare.iceConnectionStateError', + description: 'Error message for ice connection state failure', + }, }); const BROWSER_RESULTS = browser(); @@ -39,6 +44,7 @@ const isMobileBrowser = (BROWSER_RESULTS ? BROWSER_RESULTS.mobile : false) || BROWSER_RESULTS.os.includes('Android') : // mobile flag doesn't always work false); const screenSharingCheck = Meteor.settings.public.kurento.enableScreensharing; +const ICE_CONNECTION_FAILED = 'ICE connection failed'; const DesktopShare = ({ intl, @@ -46,8 +52,19 @@ const DesktopShare = ({ handleUnshareScreen, isVideoBroadcasting, isUserPresenter, -}) => ( - (screenSharingCheck && !isMobileBrowser && isUserPresenter ? +}) => { + const onFail = (error) => { + switch (error) { + case ICE_CONNECTION_FAILED: + kurentoExitScreenShare(); + logger.error('Ice connection state error'); + notify(intl.formatMessage(intlMessages.iceConnectionStateError), 'error', 'desktop'); + break; + default: + logger.error(error || 'Default error handler'); + } + }; + return (screenSharingCheck && !isMobileBrowser && isUserPresenter ? <Button className={styles.button} icon={isVideoBroadcasting ? 'desktop_off' : 'desktop'} @@ -60,11 +77,11 @@ const DesktopShare = ({ hideLabel circle size="lg" - onClick={isVideoBroadcasting ? handleUnshareScreen : handleShareScreen} + onClick={isVideoBroadcasting ? handleUnshareScreen : () => handleShareScreen(onFail)} id={isVideoBroadcasting ? 'unshare-screen-button' : 'share-screen-button'} /> - : null) -); + : null); +}; DesktopShare.propTypes = propTypes; export default injectIntl(DesktopShare); diff --git a/bigbluebutton-html5/imports/ui/components/screenshare/service.js b/bigbluebutton-html5/imports/ui/components/screenshare/service.js index 2c423fa034b862b0511cbdd3fc7c9a9829ed2b17..99e61aae0b1506660d33dcb707793e1756b87852 100644 --- a/bigbluebutton-html5/imports/ui/components/screenshare/service.js +++ b/bigbluebutton-html5/imports/ui/components/screenshare/service.js @@ -33,8 +33,8 @@ const presenterScreenshareHasStarted = () => { KurentoBridge.kurentoWatchVideo(); } -const shareScreen = () => { - KurentoBridge.kurentoShareScreen(); +const shareScreen = (onFail) => { + KurentoBridge.kurentoShareScreen(onFail); } const unshareScreen = () => { diff --git a/bigbluebutton-html5/private/locales/en.json b/bigbluebutton-html5/private/locales/en.json index c06cedcfebdc36e3a79aeef9c28546d26b5f7baf..f4a01c30d4a5840d9763a1269c6cdab8c71975a8 100755 --- a/bigbluebutton-html5/private/locales/en.json +++ b/bigbluebutton-html5/private/locales/en.json @@ -349,6 +349,7 @@ "app.video.stats.rtt": "RTT", "app.video.stats.encodeUsagePercent": "Encode usage", "app.video.stats.currentDelay": "Current delay", + "app.deskshare.iceConnectionStateError": "Error 1108: ICE connection failed when sharing screen", "app.sfu.mediaServerConnectionError2000": "Error 2000: Unable to connect to media server", "app.sfu.mediaServerOffline2001": "Error 2001: Media server is offline. Please try again later.", "app.sfu.mediaServerNoResources2002": "Error 2002: Media server has no available resources",