diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js b/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js index 9e32b75222d65233e8b87575bce910703e69a4da..266cd94f69ddc4018d5899876b2855f742eb0941 100755 --- a/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js +++ b/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js @@ -39,13 +39,14 @@ export default class KurentoAudioBridge extends BaseAudioBridge { } static normalizeError(error = {}) { - const errorMessage = error.message || error.name || error.reason || 'Unknown error'; - const errorCode = error.code || 'Undefined code'; + const errorMessage = error.name || error.message || error.reason || 'Unknown error'; + const errorCode = error.code || undefined; const errorReason = error.reason || error.id || 'Undefined reason'; return { errorMessage, errorCode, errorReason }; } + joinAudio({ isListenOnly, inputStream }, callback) { return new Promise(async (resolve, reject) => { this.callback = callback; diff --git a/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js b/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js index ee0fc23d555e44694b398c55545befed1892afcb..125c13e7b300ce63f680600db65440eb194ed6b7 100755 --- a/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js +++ b/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js @@ -22,34 +22,48 @@ const getSessionToken = () => Auth.sessionToken; export default class KurentoScreenshareBridge { static normalizeError(error = {}) { - const errorMessage = error.message || error.name || error.reason || 'Unknown error'; - const errorCode = error.code || 'Undefined code'; + const errorMessage = error.name || error.message || error.reason || 'Unknown error'; + const errorCode = error.code || undefined; const errorReason = error.reason || error.id || 'Undefined reason'; return { errorMessage, errorCode, errorReason }; } - static handlePresenterFailure(error) { + static handlePresenterFailure(error, started = false) { const normalizedError = KurentoScreenshareBridge.normalizeError(error); - logger.error({ - logCode: 'screenshare_presenter_error_failed_to_connect', - extraInfo: { ...normalizedError }, - }, `Screenshare presenter failed when trying to start due to ${normalizedError.errorMessage}`); + if (!started) { + logger.error({ + logCode: 'screenshare_presenter_error_failed_to_connect', + extraInfo: { ...normalizedError }, + }, `Screenshare presenter failed when trying to start due to ${normalizedError.errorMessage}`); + } else { + logger.error({ + logCode: 'screenshare_presenter_error_failed_after_success', + extraInfo: { ...normalizedError }, + }, `Screenshare presenter failed during working session due to ${normalizedError.errorMessage}`); + } return normalizedError; } - static handleViewerFailure(error) { + static handleViewerFailure(error, started = false) { const normalizedError = KurentoScreenshareBridge.normalizeError(error); - logger.error({ - logCode: 'screenshare_viewer_error_failed_to_connect', - extraInfo: { ...normalizedError }, - }, `Screenshare viewer failed when trying to start due to ${normalizedError.errorMessage}`); - + if (!started) { + logger.error({ + logCode: 'screenshare_viewer_error_failed_to_connect', + extraInfo: { ...normalizedError }, + }, `Screenshare viewer failed when trying to start due to ${normalizedError.errorMessage}`); + } else { + logger.error({ + logCode: 'screenshare_viewer_error_failed_after_success', + extraInfo: { ...normalizedError }, + }, `Screenshare viewer failed during working session due to ${normalizedError.errorMessage}`); + } return normalizedError; } async kurentoWatchVideo() { let iceServers = []; + let started = false; try { iceServers = await fetchWebRTCMappedStunTurnServers(getSessionToken()); @@ -110,13 +124,14 @@ export default class KurentoScreenshareBridge { }; const onFail = (error) => { - KurentoScreenshareBridge.handleViewerFailure(error); + KurentoScreenshareBridge.handleViewerFailure(error, started); }; // Callback for the kurento-extension.js script. It's called when the whole // negotiation with SFU is successful. This will load the stream into the // screenshare media element and play it manually. const onSuccess = () => { + started = true; const { webRtcPeer } = window.kurentoManager.kurentoVideo; if (webRtcPeer) { const stream = webRtcPeer.getRemoteStream(); @@ -160,12 +175,15 @@ export default class KurentoScreenshareBridge { logger, }; + let started = false; + const failureCallback = (error) => { - const normalizedError = KurentoScreenshareBridge.handlePresenterFailure(error); + const normalizedError = KurentoScreenshareBridge.handlePresenterFailure(error, started); onFail(normalizedError); }; const successCallback = () => { + started = true; logger.info({ logCode: 'screenshare_presenter_start_success', }, 'Screenshare presenter started succesfully'); 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 5953328500520999dfe4498b73036f485b70f253..ed96a0259a09130c1a30998a5c27180d584b8d81 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 @@ -41,10 +41,62 @@ const intlMessages = defineMessages({ id: 'app.actionsBar.actionsDropdown.stopDesktopShareDesc', description: 'adds context to stop desktop share option', }, - iceConnectionStateError: { + genericError: { + id: 'app.screenshare.genericError', + description: 'error message for when screensharing fails with unknown error', + }, + NotAllowedError: { + id: 'app.screenshare.notAllowed', + description: 'error message when screen access was not granted', + }, + NotSupportedError: { + id: 'app.screenshare.notSupportedError', + description: 'error message when trying to share screen in unsafe environments', + }, + NotReadableError: { + id: 'app.screenshare.notReadableError', + description: 'error message when the browser failed to capture the screen', + }, + 1108: { id: 'app.deskshare.iceConnectionStateError', description: 'Error message for ice connection state failure', }, + 2000: { + id: 'app.sfu.mediaServerConnectionError2000', + description: 'Error message fired when the SFU cannot connect to the media server', + }, + 2001: { + id: 'app.sfu.mediaServerOffline2001', + description: 'error message when SFU is offline', + }, + 2002: { + id: 'app.sfu.mediaServerNoResources2002', + description: 'Error message fired when the media server lacks disk, CPU or FDs', + }, + 2003: { + id: 'app.sfu.mediaServerRequestTimeout2003', + description: 'Error message fired when requests are timing out due to lack of resources', + }, + 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', + }, + 2200: { + id: 'app.sfu.mediaGenericError2200', + description: 'Error message fired when the SFU component generated a generic error', + }, + 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', + }, }); const BROWSER_RESULTS = browser(); @@ -53,8 +105,6 @@ const isMobileBrowser = (BROWSER_RESULTS ? BROWSER_RESULTS.mobile : false) ? BROWSER_RESULTS.os.includes('Android') // mobile flag doesn't always work : false); -const ICE_CONNECTION_FAILED = 'ICE connection failed'; - const DesktopShare = ({ intl, handleShareScreen, @@ -66,23 +116,29 @@ const DesktopShare = ({ isMeteorConnected, screenshareDataSavingSetting, }) => { - const onFail = (error) => { - switch (error) { - case ICE_CONNECTION_FAILED: - kurentoExitScreenShare(); - logger.error({ logCode: 'desktopshare_iceconnectionstate_error' }, 'ICE connection state error'); - notify(intl.formatMessage(intlMessages.iceConnectionStateError), 'error', 'desktop'); - break; - default: - logger.error({ - logCode: 'desktopshare_default_error', - extraInfo: { - maybeError: error || 'Default error handler', - }, - }, 'Default error handler for screenshare'); + // This is the failure callback that will be passed to the /api/screenshare/kurento.js + // script on the presenter's call + const onFail = (normalizedError) => { + const { errorCode, errorMessage, errorReason } = normalizedError; + const error = errorCode || errorMessage || errorReason; + // We have a properly mapped error for this. Exit screenshare and show a toast notification + if (intlMessages[error]) { + window.kurentoExitScreenShare(); + notify(intl.formatMessage(intlMessages[error]), 'error', 'desktop'); + } else { + // Unmapped error. Log it (so we can infer what's going on), close screenSharing + // session and display generic error message + logger.error({ + logCode: 'screenshare_default_error', + extraInfo: { + errorCode, errorMessage, errorReason, + }, + }, 'Default error handler for screenshare'); + window.kurentoExitScreenShare(); + notify(intl.formatMessage(intlMessages.genericError), 'error', 'desktop'); } // Don't trigger the screen share end alert if presenter click to cancel on screen share dialog - if (error && error.name !== 'NotAllowedError') { + if (error !== 'NotAllowedError') { screenShareEndAlert(); } }; @@ -100,7 +156,7 @@ const DesktopShare = ({ ? ( <Button className={cx(styles.button, isVideoBroadcasting || styles.btn)} - disabled={!isMeteorConnected && !isVideoBroadcasting || !screenshareDataSavingSetting} + disabled={(!isMeteorConnected && !isVideoBroadcasting) || !screenshareDataSavingSetting} icon={isVideoBroadcasting ? 'desktop' : 'desktop_off'} label={intl.formatMessage(vLabel)} description={intl.formatMessage(vDescr)} diff --git a/bigbluebutton-html5/private/locales/en.json b/bigbluebutton-html5/private/locales/en.json index 82b37c50c318e4302bc1bb3b59f7b4cc55a4f75b..3cfeb03dac6eeaece7227d81a8960dce98d73098 100755 --- a/bigbluebutton-html5/private/locales/en.json +++ b/bigbluebutton-html5/private/locales/en.json @@ -115,6 +115,10 @@ "app.media.screenshare.safariNotSupported": "Screenshare is currently not supported by Safari. Please, use Firefox or Google Chrome.", "app.media.screenshare.autoplayBlockedDesc": "We need your permission to show you the presenter's screen.", "app.media.screenshare.autoplayAllowLabel": "View shared screen", + "app.screenshare.notAllowed": "Error: Permission to access screen wasn't granted.", + "app.screenshare.notSupportedError": "Error: Screensharing is allowed only on safe (SSL) domains", + "app.screenshare.notReadableError": "Error: There was a failure while trying to capture your screen", + "app.screenshare.genericError": "Error: An error has occurred with screensharing, please try again", "app.meeting.ended": "This session has ended", "app.meeting.meetingTimeRemaining": "Meeting time remaining: {0}", "app.meeting.meetingTimeHasEnded": "Time ended. Meeting will close soon", diff --git a/bigbluebutton-html5/public/compatibility/kurento-extension.js b/bigbluebutton-html5/public/compatibility/kurento-extension.js index 2c9298c441058b58aa62b849f5cc3d91add04261..e74f643598a7b0c035b973c997e897ab0d36a283 100755 --- a/bigbluebutton-html5/public/compatibility/kurento-extension.js +++ b/bigbluebutton-html5/public/compatibility/kurento-extension.js @@ -492,7 +492,7 @@ Kurento.prototype.startScreensharing = function () { logCode: 'kurentoextension_screenshare_presenter_ice_failed', extraInfo: { iceConnectionState } }, `WebRTC peer for screenshare presenter failed due to ICE transitioning to ${iceConnectionState}`); - this.onFail('ICE connection failed'); + this.onFail({ message: 'iceConnectionStateError', code: 1108 }); } } }; @@ -555,7 +555,7 @@ Kurento.prototype.viewer = function () { logCode: 'kurentoextension_screenshare_viewer_ice_failed', extraInfo: { iceConnectionState } }, `WebRTC peer for screenshare viewer failed due to ICE transitioning to ${iceConnectionState}`); - this.onFail('ICE connection failed'); + this.onFail({ message: 'iceConnectionStateError', code: 1108 }); } } };