From 48b219111df37b442eedd4122a639c6143d8553d Mon Sep 17 00:00:00 2001 From: tsareg Date: Wed, 25 May 2016 15:04:48 +0300 Subject: [PATCH 01/12] Use special JitsiTrackError object instead just strings for various types of errors that may happen to JitsiTrack --- conference.js | 7 +++++-- nwjs-integration/index.html | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/conference.js b/conference.js index 858093133..c65e86dca 100644 --- a/conference.js +++ b/conference.js @@ -977,18 +977,21 @@ export default { this.videoSwitchInProgress = false; this.toggleScreenSharing(false); - if(err === TrackErrors.CHROME_EXTENSION_USER_CANCELED) + if (err.name === TrackErrors.CHROME_EXTENSION_USER_CANCELED) { return; + } console.error('failed to share local desktop', err); - if (err === TrackErrors.FIREFOX_EXTENSION_NEEDED) { + if (err.name === TrackErrors.FIREFOX_EXTENSION_NEEDED) { APP.UI.showExtensionRequiredDialog( config.desktopSharingFirefoxExtensionURL ); return; } + // TODO: handle Permission error + // Handling: // TrackErrors.CHROME_EXTENSION_INSTALLATION_ERROR // TrackErrors.GENERAL diff --git a/nwjs-integration/index.html b/nwjs-integration/index.html index f345d0e4c..fce912d88 100644 --- a/nwjs-integration/index.html +++ b/nwjs-integration/index.html @@ -36,7 +36,10 @@ }; navigator.webkitGetUserMedia({ audio: false, video: vid_constraint - }, callback, errorCallback); + }, callback, function (error) { + errorCallback && + errorCallback(error, vid_constraint); + }); } ); } From 448fcf36b6c9351b8176f58aede21fa7ed6dba06 Mon Sep 17 00:00:00 2001 From: tsareg Date: Thu, 26 May 2016 11:53:02 +0300 Subject: [PATCH 02/12] Show dialog for GUM errors --- conference.js | 111 +++++++++++++----- lang/main.json | 14 ++- modules/UI/UI.js | 95 ++++++++++++++- .../UI/side_pannels/settings/SettingsMenu.js | 22 ++++ 4 files changed, 211 insertions(+), 31 deletions(-) diff --git a/conference.js b/conference.js index c65e86dca..c2de11ff3 100644 --- a/conference.js +++ b/conference.js @@ -375,17 +375,39 @@ export default { }; } + let audioAndVideoError, audioOnlyError; + return JitsiMeetJS.init(config).then(() => { return Promise.all([ // try to retrieve audio and video createLocalTracks(['audio', 'video']) // if failed then try to retrieve only audio - .catch(() => createLocalTracks(['audio'])) + .catch(err => { + audioAndVideoError = err; + return createLocalTracks(['audio']); + }) // if audio also failed then just return empty array - .catch(() => []), + .catch(err => { + audioOnlyError = err; + return []; + }), connect(options.roomName) ]); }).then(([tracks, con]) => { + if (audioAndVideoError) { + if (audioOnlyError) { + // If both requests for 'audio' + 'video' and 'audio' only + // failed, we assume that there is some problems with user's + // microphone and show corresponding dialog. + APP.UI.showDeviceErrorDialog('microphone', audioOnlyError); + } else { + // If request for 'audio' + 'video' failed, but request for + // 'audio' only was OK, we assume that we had problems with + // camera and show corresponding dialog. + APP.UI.showDeviceErrorDialog('camera', audioAndVideoError); + } + } + console.log('initialized with %s local tracks', tracks.length); APP.connection = connection = con; this._createRoom(tracks); @@ -541,9 +563,11 @@ export default { } function createNewTracks(type, cameraDeviceId, micDeviceId) { + let audioOnlyError, videoOnlyError; + return createLocalTracks(type, cameraDeviceId, micDeviceId) .then(onTracksCreated) - .catch(() => { + .catch((err) => { // if we tried to create both audio and video tracks // at once and failed, let's try again only with // audio. Such situation may happen in case if we @@ -553,16 +577,21 @@ export default { && type.indexOf('video') !== -1) { return createLocalTracks(['audio'], null, micDeviceId); + } else if (type.indexOf('audio') !== -1) { + audioOnlyError = err; + } else if (type.indexOf('video') !== -1) { + videoOnlyError = err; } }) .then(onTracksCreated) - .catch(() => { + .catch((err) => { // if we tried to create both audio and video tracks // at once and failed, let's try again only with // video. Such situation may happen in case if we // granted access only to camera, but not to // microphone. + audioOnlyError = err; if (type.indexOf('audio') !== -1 && type.indexOf('video') !== -1) { return createLocalTracks(['video'], @@ -571,8 +600,18 @@ export default { } }) .then(onTracksCreated) - .catch(() => { - // can't do anything in this case, so just ignore; + .catch((err) => { + videoOnlyError = err; + + if (videoOnlyError) { + APP.UI.showDeviceErrorDialog( + 'camera', videoOnlyError); + } + + if (audioOnlyError) { + APP.UI.showDeviceErrorDialog( + 'microphone', audioOnlyError); + } }); } @@ -896,13 +935,13 @@ export default { this.isSharingScreen = stream.videoType === 'desktop'; APP.UI.addLocalStream(stream); + + stream.videoType === 'camera' && APP.UI.enableCameraButton(); } else { this.videoMuted = false; this.isSharingScreen = false; } - stream.videoType === 'camera' && APP.UI.enableCameraButton(); - APP.UI.setVideoMuted(this.localId, this.videoMuted); APP.UI.updateDesktopSharingButtons(); @@ -990,21 +1029,22 @@ export default { return; } - // TODO: handle Permission error - // Handling: + // TrackErrors.PERMISSION_DENIED // TrackErrors.CHROME_EXTENSION_INSTALLATION_ERROR // TrackErrors.GENERAL // and any other - let dialogTxt = APP.translation - .generateTranslationHTML("dialog.failtoinstall"); - let dialogTitle = APP.translation - .generateTranslationHTML("dialog.error"); - APP.UI.messageHandler.openDialog( - dialogTitle, - dialogTxt, - false - ); + let dialogTxt = APP.translation.generateTranslationHTML( + err.name === TrackErrors.PERMISSION_DENIED + ? "dialog.screenSharingPermissionDeniedError" + : "dialog.failtoinstall"); + + let dialogTitle = APP.translation.generateTranslationHTML( + err.name === TrackErrors.PERMISSION_DENIED + ? "dialog.permissionDenied" + : "dialog.error"); + + APP.UI.messageHandler.openDialog(dialogTitle, dialogTxt, false); }); } else { createLocalTracks(['video']).then( @@ -1016,6 +1056,8 @@ export default { this.useVideoStream(null); this.videoSwitchInProgress = false; console.error('failed to share local video', err); + + APP.UI.showDeviceErrorDialog('camera', err); }); } }, @@ -1357,22 +1399,32 @@ export default { APP.UI.addListener( UIEvents.VIDEO_DEVICE_CHANGED, (cameraDeviceId) => { - APP.settings.setCameraDeviceId(cameraDeviceId); - createLocalTracks(['video']).then(([stream]) => { - this.useVideoStream(stream); - console.log('switched local video device'); - }); + createLocalTracks(['video']) + .then(([stream]) => { + this.useVideoStream(stream); + console.log('switched local video device'); + APP.settings.setCameraDeviceId(cameraDeviceId); + }) + .catch((err) => { + APP.UI.showDeviceErrorDialog('camera', err); + APP.UI.setSelectedCameraFromSettings(); + }); } ); APP.UI.addListener( UIEvents.AUDIO_DEVICE_CHANGED, (micDeviceId) => { - APP.settings.setMicDeviceId(micDeviceId); - createLocalTracks(['audio']).then(([stream]) => { - this.useAudioStream(stream); - console.log('switched local audio device'); - }); + createLocalTracks(['audio']) + .then(([stream]) => { + this.useAudioStream(stream); + console.log('switched local audio device'); + APP.settings.setMicDeviceId(micDeviceId); + }) + .catch((err) => { + APP.UI.showDeviceErrorDialog('microphone', err); + APP.UI.setSelectedMicFromSettings(); + }); } ); @@ -1383,6 +1435,7 @@ export default { .then(() => console.log('changed audio output device')) .catch((err) => { console.error('failed to set audio output device', err); + APP.UI.setSelectedAudioOutputFromSettings(); }); } ); diff --git a/lang/main.json b/lang/main.json index 62539cad0..0007c544f 100644 --- a/lang/main.json +++ b/lang/main.json @@ -222,7 +222,19 @@ "stopStreamingWarning": "Are you sure you would like to stop the live streaming?", "stopRecordingWarning": "Are you sure you would like to stop the recording?", "stopLiveStreaming": "Stop live streaming", - "stopRecording": "Stop recording" + "stopRecording": "Stop recording", + "doNotShowWarningAgain": "Don't show this warning again", + "permissionDenied": "Permission Denied", + "screenSharingPermissionDeniedError": "You have not granted permission to share your screen.", + "cameraUnsupportedResolutionError": "Your camera does not support required video resolution.", + "cameraUnknownError": "Cannot use camera for a unknown reason.", + "cameraPermissionDeniedError": "You have not granted permission to use your camera.", + "cameraNotFoundError": "Requested camera was not found.", + "cameraConstraintFailedError": "Yor camera does not satisfy some of required constraints.", + "micUnknownError": "Cannot use microphone for a unknown reason.", + "micPermissionDeniedError": "You have not granted permission to use your microphone.", + "micNotFoundError": "Requested microphone was not found.", + "micConstraintFailedError": "Yor microphone does not satisfy some of required constraints." }, "email": { diff --git a/modules/UI/UI.js b/modules/UI/UI.js index 612f407c8..89e1589d0 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -1,4 +1,4 @@ -/* global APP, $, config, interfaceConfig, toastr */ +/* global APP, JitsiMeetJS, $, config, interfaceConfig, toastr */ /* jshint -W101 */ var UI = {}; @@ -38,6 +38,32 @@ let sharedVideoManager; let followMeHandler; +const TrackErrors = JitsiMeetJS.errors.track; + +const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = { + microphone: {}, + camera: {} +}; + +JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.UNSUPPORTED_RESOLUTION] + = "dialog.cameraUnsupportedResolutionError"; +JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.GENERAL] + = "dialog.cameraUnknownError"; +JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.PERMISSION_DENIED] + = "dialog.cameraPermissionDeniedError"; +JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.NOT_FOUND] + = "dialog.cameraNotFoundError"; +JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.CONSTRAINT_FAILED] + = "dialog.cameraConstraintFailedError"; +JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.GENERAL] + = "dialog.micUnknownError"; +JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.PERMISSION_DENIED] + = "dialog.micPermissionDeniedError"; +JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.NOT_FOUND] + = "dialog.micNotFoundError"; +JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.CONSTRAINT_FAILED] + = "dialog.micConstraintFailedError"; + /** * Prompt user for nickname. */ @@ -1078,6 +1104,28 @@ UI.onAvailableDevicesChanged = function (devices) { SettingsMenu.changeDevicesList(devices); }; +/** + * Sets microphone's element to select camera ID from settings. + */ +UI.setSelectedCameraFromSettings = function () { + SettingsMenu.setSelectedCameraFromSettings(); +}; + +/** + * Sets audio outputs's + + `; + + messageHandler.openDialog( + title, + message, + false, + {Ok: true}, + function () { + let form = $.prompt.getPrompt(), + input = form.find("#doNotShowWarningAgain"); + + window.localStorage[type + "DoNotShowErrorAgain-" + error.name] + = input.prop("checked"); + } + ); + + APP.translation.translateElement($(".jqibox")); +}; + UI.updateDevicesAvailability = function (id, devices) { VideoLayout.setDeviceAvailabilityIcons(id, devices); }; diff --git a/modules/UI/side_pannels/settings/SettingsMenu.js b/modules/UI/side_pannels/settings/SettingsMenu.js index bee62594f..58c78b3e3 100644 --- a/modules/UI/side_pannels/settings/SettingsMenu.js +++ b/modules/UI/side_pannels/settings/SettingsMenu.js @@ -203,6 +203,28 @@ export default { $('#avatar').attr('src', avatarUrl); }, + /** + * Sets microphone's element to select camera ID from settings. + */ + setSelectedCameraFromSettings () { + $('#selectCamera').val(Settings.getCameraDeviceId()); + }, + + /** + * Sets audio outputs's - - `; + message = "

" + + (!JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP[type][error.name] + && error.message + ? "
" + error.message + "
" + : "") + + ""; messageHandler.openDialog( title, @@ -1188,11 +1194,16 @@ UI.showDeviceErrorDialog = function (type, error) { false, {Ok: true}, function () { - let form = $.prompt.getPrompt(), - input = form.find("#doNotShowWarningAgain"); + let form = $.prompt.getPrompt(); - window.localStorage[type + "DoNotShowErrorAgain-" + error.name] - = input.prop("checked"); + if (form) { + let input = form.find("#doNotShowWarningAgain"); + + if (input.length) { + window.localStorage[type + "DoNotShowErrorAgain-" + + error.name] = input.prop("checked"); + } + } } ); From f574dbe056c2241ca1ced7a2e3b2a698cfbaaf94 Mon Sep 17 00:00:00 2001 From: tsareg Date: Fri, 27 May 2016 18:49:26 +0300 Subject: [PATCH 04/12] Changes after code review --- conference.js | 143 ++++++++++++++++++++++++++++------------------- lang/main.json | 2 + modules/UI/UI.js | 129 ++++++++++++++++++++++++++++++++---------- 3 files changed, 186 insertions(+), 88 deletions(-) diff --git a/conference.js b/conference.js index 09d33d011..95de0ae20 100644 --- a/conference.js +++ b/conference.js @@ -399,12 +399,12 @@ export default { // If both requests for 'audio' + 'video' and 'audio' only // failed, we assume that there is some problems with user's // microphone and show corresponding dialog. - APP.UI.showDeviceErrorDialog('microphone', audioOnlyError); + APP.UI.showDeviceErrorDialog(audioOnlyError, null); } else { // If request for 'audio' + 'video' failed, but request for // 'audio' only was OK, we assume that we had problems with // camera and show corresponding dialog. - APP.UI.showDeviceErrorDialog('camera', audioAndVideoError); + APP.UI.showDeviceErrorDialog(null, audioAndVideoError); } } @@ -563,56 +563,78 @@ export default { } function createNewTracks(type, cameraDeviceId, micDeviceId) { - let audioOnlyError, videoOnlyError; + let audioTrackCreationError; + let videoTrackCreationError; + let audioRequested = type.indexOf('audio') !== -1; + let videoRequested = type.indexOf('video') !== -1; + let promise; - return createLocalTracks(type, cameraDeviceId, micDeviceId) - .then(onTracksCreated) - .catch((err) => { - // if we tried to create both audio and video tracks - // at once and failed, let's try again only with - // audio. Such situation may happen in case if we - // granted access only to microphone, but not to - // camera. - if (type.indexOf('audio') !== -1 - && type.indexOf('video') !== -1) { - return createLocalTracks(['audio'], null, - micDeviceId); - } else if (type.indexOf('audio') !== -1) { - audioOnlyError = err; - } else if (type.indexOf('video') !== -1) { - videoOnlyError = err; - } + if (audioRequested && micDeviceId !== null) { + if (videoRequested && cameraDeviceId !== null) { + promise = createLocalTracks( + type, cameraDeviceId, micDeviceId) + .catch(() => { + return Promise.all([ + createAudioTrack(false), + createVideoTrack(false)]); + }) + .then((audioTracks, videoTracks) => { + if (audioTrackCreationError) { + if (videoTrackCreationError) { + APP.UI.showDeviceErrorDialog( + audioTrackCreationError, + videoTrackCreationError); + } else { + APP.UI.showDeviceErrorDialog( + audioTrackCreationError, + null); + } + } else if (videoTrackCreationError) { + APP.UI.showDeviceErrorDialog( + null, + videoTrackCreationError); + } - }) - .then(onTracksCreated) - .catch((err) => { - // if we tried to create both audio and video tracks - // at once and failed, let's try again only with - // video. Such situation may happen in case if we - // granted access only to camera, but not to - // microphone. - audioOnlyError = err; - if (type.indexOf('audio') !== -1 - && type.indexOf('video') !== -1) { - return createLocalTracks(['video'], - cameraDeviceId, - null); - } - }) - .then(onTracksCreated) - .catch((err) => { - videoOnlyError = err; + return audioTracks.concat(videoTracks); + }); + } else { + promise = createAudioTrack(); + } + } else if (videoRequested && cameraDeviceId !== null) { + promise = createVideoTrack(); + } else { + promise = Promise.resolve([]); + } - if (videoOnlyError) { - APP.UI.showDeviceErrorDialog( - 'camera', videoOnlyError); - } + return promise + .then(onTracksCreated); - if (audioOnlyError) { - APP.UI.showDeviceErrorDialog( - 'microphone', audioOnlyError); - } - }); + function createAudioTrack(showError) { + return createLocalTracks(['audio'], null, micDeviceId) + .catch(err => { + audioTrackCreationError = err; + + if (showError) { + APP.UI.showDeviceErrorDialog(err, null); + } + + return []; + }); + } + + function createVideoTrack(showError) { + return createLocalTracks( + ['video'], cameraDeviceId, null) + .catch(err => { + videoTrackCreationError = err; + + if (showError) { + APP.UI.showDeviceErrorDialog(null, err); + } + + return []; + }); + } } function onTracksCreated(tracks) { @@ -1034,15 +1056,20 @@ export default { // TrackErrors.CHROME_EXTENSION_INSTALLATION_ERROR // TrackErrors.GENERAL // and any other - let dialogTxt = APP.translation.generateTranslationHTML( - err.name === TrackErrors.PERMISSION_DENIED - ? "dialog.screenSharingPermissionDeniedError" - : "dialog.failtoinstall"); + let dialogTxt; + let dialogTitle; - let dialogTitle = APP.translation.generateTranslationHTML( - err.name === TrackErrors.PERMISSION_DENIED - ? "dialog.permissionDenied" - : "dialog.error"); + if (err.name === TrackErrors.PERMISSION_DENIED) { + dialogTxt = APP.translation.generateTranslationHTML( + "dialog.screenSharingPermissionDeniedError"); + dialogTitle = APP.translation.generateTranslationHTML( + "dialog.error"); + } else { + dialogTxt = APP.translation.generateTranslationHTML( + "dialog.failtoinstall"); + dialogTitle = APP.translation.generateTranslationHTML( + "dialog.permissionDenied"); + } APP.UI.messageHandler.openDialog(dialogTitle, dialogTxt, false); }); @@ -1404,7 +1431,7 @@ export default { APP.settings.setCameraDeviceId(cameraDeviceId); }) .catch((err) => { - APP.UI.showDeviceErrorDialog('camera', err); + APP.UI.showDeviceErrorDialog(null, err); APP.UI.setSelectedCameraFromSettings(); }); } @@ -1420,7 +1447,7 @@ export default { APP.settings.setMicDeviceId(micDeviceId); }) .catch((err) => { - APP.UI.showDeviceErrorDialog('microphone', err); + APP.UI.showDeviceErrorDialog(err, null); APP.UI.setSelectedMicFromSettings(); }); } diff --git a/lang/main.json b/lang/main.json index 0007c544f..2cc4ab304 100644 --- a/lang/main.json +++ b/lang/main.json @@ -226,6 +226,8 @@ "doNotShowWarningAgain": "Don't show this warning again", "permissionDenied": "Permission Denied", "screenSharingPermissionDeniedError": "You have not granted permission to share your screen.", + "micErrorPresent": "There was an error connecting to your microphone.", + "cameraErrorPresent": "There was an error connecting to your camera.", "cameraUnsupportedResolutionError": "Your camera does not support required video resolution.", "cameraUnknownError": "Cannot use camera for a unknown reason.", "cameraPermissionDeniedError": "You have not granted permission to use your camera.", diff --git a/modules/UI/UI.js b/modules/UI/UI.js index 40b12180d..8b50f3041 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -1155,41 +1155,93 @@ UI.showExtensionRequiredDialog = function (url) { }; /** - * Shows dialog with information about camera or microphone error. - * @param {'microphone'|'camera'} type - * @param {JitsiTrackError} error + * Shows dialog with combined information about camera and microphone errors. + * @param {JitsiTrackError} micError + * @param {JitsiTrackError} cameraError */ -UI.showDeviceErrorDialog = function (type, error) { - if (type !== "microphone" && type !== "camera") { - throw new Error("Invalid device type"); +UI.showDeviceErrorDialog = function (micError, cameraError) { + let localStoragePropName = "doNotShowErrorAgain"; + let isMicJitsiTrackErrorAndHasName = micError && micError.name && + micError instanceof JitsiMeetJS.JitsiTrackError; + let isCameraJitsiTrackErrorAndHasName = cameraError && cameraError.name && + cameraError instanceof JitsiMeetJS.JitsiTrackError; + let showDoNotShowWarning = false; + + if (micError && cameraError && isMicJitsiTrackErrorAndHasName && + isCameraJitsiTrackErrorAndHasName) { + showDoNotShowWarning = true; + } else if (micError && isMicJitsiTrackErrorAndHasName && !cameraError) { + showDoNotShowWarning = true; + } else if (cameraError && isCameraJitsiTrackErrorAndHasName && !micError) { + showDoNotShowWarning = true; } - if (error.name && error instanceof JitsiMeetJS.JitsiTrackError && - window.localStorage[type + "DoNotShowErrorAgain-" + error.name] - === "true") { - return; + if (micError) { + localStoragePropName += "-mic-" + micError.name; } - let titleKey = error.name === TrackErrors.PERMISSION_DENIED - ? "dialog.permissionDenied" - : "dialog.error", - errorMsg = JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP[type][error.name] || - JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP[type][TrackErrors.GENERAL], - title = ``, - message = "

" + - (!JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP[type][error.name] - && error.message - ? "
" + error.message + "
" - : "") + - ""; + if (cameraError) { + localStoragePropName += "-camera-" + cameraError.name; + } + + if (showDoNotShowWarning) { + if (window.localStorage[localStoragePropName] === "true") { + return; + } + } + + let title = getTitleKey(); + let titleMsg = ``; + let cameraJitsiTrackErrorMsg = cameraError + ? JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[cameraError.name] + : undefined; + let micJitsiTrackErrorMsg = micError + ? JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[micError.name] + : undefined; + let cameraErrorMsg = cameraError + ? cameraJitsiTrackErrorMsg || + JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.GENERAL] + : ""; + let micErrorMsg = micError + ? micJitsiTrackErrorMsg || + JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.GENERAL] + : ""; + let additionalCameraErrorMsg = !cameraJitsiTrackErrorMsg && cameraError && + cameraError.message + ? `
${cameraError.message}
` + : ``; + let additionalMicErrorMsg = !micJitsiTrackErrorMsg && micError && + micError.message + ? `
${micError.message}
` + : ``; + let doNotShowWarningAgainSection = showDoNotShowWarning + ? `` + : ``; + let message = ''; + + if (micError) { + message = ` + ${message} +

+

+ ${additionalMicErrorMsg}`; + } + + if (cameraError) { + message = ` + ${message} +

+

+ ${additionalCameraErrorMsg}`; + } + + message = `${message}${doNotShowWarningAgainSection}`; messageHandler.openDialog( - title, + titleMsg, message, false, {Ok: true}, @@ -1200,14 +1252,31 @@ UI.showDeviceErrorDialog = function (type, error) { let input = form.find("#doNotShowWarningAgain"); if (input.length) { - window.localStorage[type + "DoNotShowErrorAgain-" - + error.name] = input.prop("checked"); + window.localStorage[localStoragePropName] = + input.prop("checked"); } } } ); APP.translation.translateElement($(".jqibox")); + + function getTitleKey() { + let title = "dialog.error"; + + if (micError && micError.name === TrackErrors.PERMISSION_DENIED) { + if (cameraError && cameraError.name === TrackErrors.PERMISSION_DENIED) { + title = "dialog.permissionDenied"; + } else if (!cameraError) { + title = "dialog.permissionDenied"; + } + } else if (cameraError && + cameraError.name === TrackErrors.PERMISSION_DENIED) { + title = "dialog.permissionDenied"; + } + + return title; + } }; UI.updateDevicesAvailability = function (id, devices) { From 6e34e33b0dfd64704aeaf7f023cc8fa670546bc1 Mon Sep 17 00:00:00 2001 From: hristoterezov Date: Wed, 1 Jun 2016 17:47:24 -0500 Subject: [PATCH 05/12] Fixes unhandled error from history.pushState --- app.js | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/app.js b/app.js index 523e55781..6778fd109 100644 --- a/app.js +++ b/app.js @@ -24,6 +24,24 @@ import API from './modules/API/API'; import UIEvents from './service/UI/UIEvents'; +/** + * Tries to push history state with the following parameters: + * 'VideoChat', `Room: ${roomName}`, URL. If fail, prints the error and returns + * it. + */ +function pushHistoryState(roomName, URL) { + try { + window.history.pushState( + 'VideoChat', `Room: ${roomName}`, URL + ); + } catch (e) { + console.warn("Push history state failed with parameters:", + 'VideoChat', `Room: ${roomName}`, URL, e); + return e; + } + return null; +} + /** * Builds and returns the room name. */ @@ -33,9 +51,14 @@ function buildRoomName () { if(!roomName) { let word = RoomnameGenerator.generateRoomWithoutSeparator(); roomName = word.toLowerCase(); - window.history.pushState( - 'VideoChat', `Room: ${word}`, window.location.pathname + word - ); + let historyURL = window.location.pathname + word; + //Trying to push state with URL "/" + roomName + var err = pushHistoryState(word, historyURL); + //If URL "/" + roomName is not good, trying with explicitly adding the + //domain name. + if(err && config.hosts.domain) { + pushHistoryState(word, "//" + config.hosts.domain + historyURL); + } } return roomName; From ed9fd6c8fd38cc01f42eb20dd741b4ef44b99843 Mon Sep 17 00:00:00 2001 From: damencho Date: Thu, 2 Jun 2016 12:27:47 -0500 Subject: [PATCH 06/12] Adds property for initial jetty ssi configuration. --- debian/jitsi-meet.postinst | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/jitsi-meet.postinst b/debian/jitsi-meet.postinst index fdf38cccc..9e8844185 100644 --- a/debian/jitsi-meet.postinst +++ b/debian/jitsi-meet.postinst @@ -90,6 +90,7 @@ case "$1" in echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.alias./config.js=/etc/jitsi/meet/$JVB_HOSTNAME-config.js" >> $JVB_CONFIG echo "org.jitsi.videobridge.rest.jetty.RewriteHandler.regex=^/([a-zA-Z0-9]+)$" >> $JVB_CONFIG echo "org.jitsi.videobridge.rest.jetty.RewriteHandler.replacement=/" >> $JVB_CONFIG + echo "org.jitsi.videobridge.rest.jetty.SSIResourceHandler.paths=/" >> $JVB_CONFIG echo "org.jitsi.videobridge.rest.jetty.tls.port=443" >> $JVB_CONFIG echo "org.jitsi.videobridge.TCP_HARVESTER_PORT=443" >> $JVB_CONFIG echo "org.jitsi.videobridge.rest.jetty.sslContextFactory.keyStorePath=/etc/jitsi/videobridge/$JVB_HOSTNAME.jks" >> $JVB_CONFIG From 3d5af92c7ac78091d02b3f00fa8f4a7b47e804fc Mon Sep 17 00:00:00 2001 From: hristoterezov Date: Thu, 2 Jun 2016 13:02:42 -0500 Subject: [PATCH 07/12] Removes unnecessary history.pushState if the welcome page is disabled and the user enter the base URL --- app.js | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/app.js b/app.js index 6778fd109..12f6a978a 100644 --- a/app.js +++ b/app.js @@ -24,24 +24,6 @@ import API from './modules/API/API'; import UIEvents from './service/UI/UIEvents'; -/** - * Tries to push history state with the following parameters: - * 'VideoChat', `Room: ${roomName}`, URL. If fail, prints the error and returns - * it. - */ -function pushHistoryState(roomName, URL) { - try { - window.history.pushState( - 'VideoChat', `Room: ${roomName}`, URL - ); - } catch (e) { - console.warn("Push history state failed with parameters:", - 'VideoChat', `Room: ${roomName}`, URL, e); - return e; - } - return null; -} - /** * Builds and returns the room name. */ @@ -51,14 +33,6 @@ function buildRoomName () { if(!roomName) { let word = RoomnameGenerator.generateRoomWithoutSeparator(); roomName = word.toLowerCase(); - let historyURL = window.location.pathname + word; - //Trying to push state with URL "/" + roomName - var err = pushHistoryState(word, historyURL); - //If URL "/" + roomName is not good, trying with explicitly adding the - //domain name. - if(err && config.hosts.domain) { - pushHistoryState(word, "//" + config.hosts.domain + historyURL); - } } return roomName; From 69798848c0f49f322ff55bf8789603853aa4fd4e Mon Sep 17 00:00:00 2001 From: yanas Date: Thu, 2 Jun 2016 11:30:45 -0500 Subject: [PATCH 08/12] Handle recording errors --- conference.js | 5 ++-- lang/main.json | 6 ++-- modules/UI/UI.js | 4 +-- modules/UI/recording/Recording.js | 46 ++++++++++++++++++++++--------- 4 files changed, 42 insertions(+), 19 deletions(-) diff --git a/conference.js b/conference.js index 858093133..66fd87c0c 100644 --- a/conference.js +++ b/conference.js @@ -21,6 +21,8 @@ const ConferenceErrors = JitsiMeetJS.errors.conference; const TrackEvents = JitsiMeetJS.events.track; const TrackErrors = JitsiMeetJS.errors.track; +const RecorderErrors = JitsiMeetJS.errors.recorder; + let room, connection, localAudio, localVideo, roomLocker; let currentAudioInputDevices, currentVideoInputDevices; @@ -356,7 +358,6 @@ export default { if(JitsiMeetJS.getGlobalOnErrorHandler){ var oldOnErrorHandler = window.onerror; window.onerror = function (message, source, lineno, colno, error) { - JitsiMeetJS.getGlobalOnErrorHandler( message, source, lineno, colno, error); @@ -1152,7 +1153,7 @@ export default { room.on(ConferenceEvents.RECORDER_STATE_CHANGED, (status, error) => { console.log("Received recorder status change: ", status, error); - APP.UI.updateRecordingState(status); + APP.UI.updateRecordingState(status, error); }); room.on(ConferenceEvents.USER_STATUS_CHANGED, function (id, status) { diff --git a/lang/main.json b/lang/main.json index 62539cad0..a982a5c58 100644 --- a/lang/main.json +++ b/lang/main.json @@ -275,7 +275,8 @@ "off": "Recording stopped", "failedToStart": "Recording failed to start", "buttonTooltip": "Start / stop recording", - "error": "Recording failed. Please try again." + "error": "Recording failed. Please try again.", + "unavailable": "The recording service is currently unavailable. Please try again later." }, "liveStreaming": { @@ -286,6 +287,7 @@ "failedToStart": "Live streaming failed to start", "buttonTooltip": "Start / stop live stream", "streamIdRequired": "Please fill in the stream id in order to launch the live streaming.", - "error": "Live streaming failed. Please try again" + "error": "Live streaming failed. Please try again.", + "busy": "All recorders are currently busy. Please try again later." } } diff --git a/modules/UI/UI.js b/modules/UI/UI.js index 612f407c8..c62afb2c6 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -1006,8 +1006,8 @@ UI.requestFeedback = function () { }); }; -UI.updateRecordingState = function (state) { - Recording.updateRecordingState(state); +UI.updateRecordingState = function (state, error) { + Recording.updateRecordingState(state, error); }; UI.notifyTokenAuthFailed = function () { diff --git a/modules/UI/recording/Recording.js b/modules/UI/recording/Recording.js index 051aeda1a..308eedd6c 100644 --- a/modules/UI/recording/Recording.js +++ b/modules/UI/recording/Recording.js @@ -206,7 +206,9 @@ var Status = { AVAILABLE: "available", UNAVAILABLE: "unavailable", PENDING: "pending", - ERROR: "error" + ERROR: "error", + FAILED: "failed", + BUSY: "busy" }; /** @@ -245,21 +247,27 @@ var Recording = { if (recordingType === 'jibri') { this.baseClass = "fa fa-play-circle"; + this.recordingTitle = "dialog.liveStreaming"; this.recordingOnKey = "liveStreaming.on"; this.recordingOffKey = "liveStreaming.off"; this.recordingPendingKey = "liveStreaming.pending"; this.failedToStartKey = "liveStreaming.failedToStart"; this.recordingErrorKey = "liveStreaming.error"; this.recordingButtonTooltip = "liveStreaming.buttonTooltip"; + this.recordingUnavailable = "liveStreaming.unavailable"; + this.recordingBusy = "liveStreaming.busy"; } else { this.baseClass = "icon-recEnable"; + this.recordingTitle = "dialog.recording"; this.recordingOnKey = "recording.on"; this.recordingOffKey = "recording.off"; this.recordingPendingKey = "recording.pending"; this.failedToStartKey = "recording.failedToStart"; this.recordingErrorKey = "recording.error"; this.recordingButtonTooltip = "recording.buttonTooltip"; + this.recordingUnavailable = "recording.unavailable"; + this.recordingBusy = "liveStreaming.busy"; } selector.addClass(this.baseClass); @@ -307,10 +315,17 @@ var Recording = { } break; } + case Status.BUSY: { + APP.UI.messageHandler.openMessageDialog( + self.recordingTitle, + self.recordingBusy + ); + break; + } default: { APP.UI.messageHandler.openMessageDialog( - "dialog.liveStreaming", - "liveStreaming.unavailable" + self.recordingTitle, + self.recordingUnavailable ); } } @@ -333,7 +348,7 @@ var Recording = { * Updates the recording state UI. * @param recordingState gives us the current recording state */ - updateRecordingState(recordingState) { + updateRecordingState(recordingState, error) { // I'm the recorder, so I don't want to see any UI related to states. if (config.iAmRecorder) return; @@ -342,16 +357,19 @@ var Recording = { if (!recordingState || this.currentState === recordingState) return; - this.updateRecordingUI(recordingState); + this.updateRecordingUI(recordingState, error); }, /** * Sets the state of the recording button. * @param recordingState gives us the current recording state */ - updateRecordingUI (recordingState) { + updateRecordingUI (recordingState, error) { let buttonSelector = $('#toolbar_button_record'); + let oldState = this.currentState; + this.currentState = recordingState; + // TODO: handle recording state=available if (recordingState === Status.ON) { @@ -361,19 +379,21 @@ var Recording = { this._updateStatusLabel(this.recordingOnKey, false); } else if (recordingState === Status.OFF - || recordingState === Status.UNAVAILABLE) { + || recordingState === Status.UNAVAILABLE + || recordingState === Status.BUSY + || recordingState === Status.FAILED) { // We don't want to do any changes if this is // an availability change. - if (this.currentState !== Status.ON - && this.currentState !== Status.PENDING) + if (oldState !== Status.ON + && oldState !== Status.PENDING) return; buttonSelector.removeClass(this.baseClass + " active"); buttonSelector.addClass(this.baseClass); let messageKey; - if (this.currentState === Status.PENDING) + if (oldState === Status.PENDING) messageKey = this.failedToStartKey; else messageKey = this.recordingOffKey; @@ -391,15 +411,15 @@ var Recording = { this._updateStatusLabel(this.recordingPendingKey, true); } - else if (recordingState === Status.ERROR) { + else if (recordingState === Status.ERROR + || recordingState === Status.FAILED) { buttonSelector.removeClass(this.baseClass + " active"); buttonSelector.addClass(this.baseClass); this._updateStatusLabel(this.recordingErrorKey, true); + console.log("Recording failed for the following reason: ", error); } - this.currentState = recordingState; - let labelSelector = $('#recordingLabel'); // We don't show the label for available state. From 7e4b13fb44f55e129e5cae923c2b7ba9e7f7e0ef Mon Sep 17 00:00:00 2001 From: yanas Date: Thu, 2 Jun 2016 15:41:28 -0500 Subject: [PATCH 09/12] Remove recorder errors import --- conference.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/conference.js b/conference.js index 66fd87c0c..2e40c431c 100644 --- a/conference.js +++ b/conference.js @@ -21,8 +21,6 @@ const ConferenceErrors = JitsiMeetJS.errors.conference; const TrackEvents = JitsiMeetJS.events.track; const TrackErrors = JitsiMeetJS.errors.track; -const RecorderErrors = JitsiMeetJS.errors.recorder; - let room, connection, localAudio, localVideo, roomLocker; let currentAudioInputDevices, currentVideoInputDevices; From 07c2e91ae27fcbac38ab0d009c9613df20c9cc93 Mon Sep 17 00:00:00 2001 From: yanas Date: Thu, 2 Jun 2016 17:12:40 -0500 Subject: [PATCH 10/12] Do not handle the actual error message yet --- conference.js | 2 +- modules/UI/UI.js | 4 ++-- modules/UI/recording/Recording.js | 7 +++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/conference.js b/conference.js index 2e40c431c..951ec2e79 100644 --- a/conference.js +++ b/conference.js @@ -1151,7 +1151,7 @@ export default { room.on(ConferenceEvents.RECORDER_STATE_CHANGED, (status, error) => { console.log("Received recorder status change: ", status, error); - APP.UI.updateRecordingState(status, error); + APP.UI.updateRecordingState(status); }); room.on(ConferenceEvents.USER_STATUS_CHANGED, function (id, status) { diff --git a/modules/UI/UI.js b/modules/UI/UI.js index c62afb2c6..612f407c8 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -1006,8 +1006,8 @@ UI.requestFeedback = function () { }); }; -UI.updateRecordingState = function (state, error) { - Recording.updateRecordingState(state, error); +UI.updateRecordingState = function (state) { + Recording.updateRecordingState(state); }; UI.notifyTokenAuthFailed = function () { diff --git a/modules/UI/recording/Recording.js b/modules/UI/recording/Recording.js index 308eedd6c..8929d1be7 100644 --- a/modules/UI/recording/Recording.js +++ b/modules/UI/recording/Recording.js @@ -348,7 +348,7 @@ var Recording = { * Updates the recording state UI. * @param recordingState gives us the current recording state */ - updateRecordingState(recordingState, error) { + updateRecordingState(recordingState) { // I'm the recorder, so I don't want to see any UI related to states. if (config.iAmRecorder) return; @@ -357,14 +357,14 @@ var Recording = { if (!recordingState || this.currentState === recordingState) return; - this.updateRecordingUI(recordingState, error); + this.updateRecordingUI(recordingState); }, /** * Sets the state of the recording button. * @param recordingState gives us the current recording state */ - updateRecordingUI (recordingState, error) { + updateRecordingUI (recordingState) { let buttonSelector = $('#toolbar_button_record'); let oldState = this.currentState; @@ -417,7 +417,6 @@ var Recording = { buttonSelector.addClass(this.baseClass); this._updateStatusLabel(this.recordingErrorKey, true); - console.log("Recording failed for the following reason: ", error); } let labelSelector = $('#recordingLabel'); From 968521ef7c70e737a26cb53613790eebedaa435b Mon Sep 17 00:00:00 2001 From: yanas Date: Fri, 3 Jun 2016 07:45:57 -0500 Subject: [PATCH 11/12] Revert "Removes unnecessary history.pushState if the welcome page is disabled and the user enter the base URL" This reverts commit 3d5af92c7ac78091d02b3f00fa8f4a7b47e804fc. --- app.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/app.js b/app.js index 12f6a978a..6778fd109 100644 --- a/app.js +++ b/app.js @@ -24,6 +24,24 @@ import API from './modules/API/API'; import UIEvents from './service/UI/UIEvents'; +/** + * Tries to push history state with the following parameters: + * 'VideoChat', `Room: ${roomName}`, URL. If fail, prints the error and returns + * it. + */ +function pushHistoryState(roomName, URL) { + try { + window.history.pushState( + 'VideoChat', `Room: ${roomName}`, URL + ); + } catch (e) { + console.warn("Push history state failed with parameters:", + 'VideoChat', `Room: ${roomName}`, URL, e); + return e; + } + return null; +} + /** * Builds and returns the room name. */ @@ -33,6 +51,14 @@ function buildRoomName () { if(!roomName) { let word = RoomnameGenerator.generateRoomWithoutSeparator(); roomName = word.toLowerCase(); + let historyURL = window.location.pathname + word; + //Trying to push state with URL "/" + roomName + var err = pushHistoryState(word, historyURL); + //If URL "/" + roomName is not good, trying with explicitly adding the + //domain name. + if(err && config.hosts.domain) { + pushHistoryState(word, "//" + config.hosts.domain + historyURL); + } } return roomName; From c2eede2bb57f939481401379800c3991a6d6c7b2 Mon Sep 17 00:00:00 2001 From: Aaron van Meerten Date: Fri, 3 Jun 2016 13:00:09 -0500 Subject: [PATCH 12/12] Only push to history with present page URL plus room name Use location.href instead of location.pathname to make the URL absolute --- app.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/app.js b/app.js index 6778fd109..1f84d4b3c 100644 --- a/app.js +++ b/app.js @@ -51,14 +51,9 @@ function buildRoomName () { if(!roomName) { let word = RoomnameGenerator.generateRoomWithoutSeparator(); roomName = word.toLowerCase(); - let historyURL = window.location.pathname + word; - //Trying to push state with URL "/" + roomName - var err = pushHistoryState(word, historyURL); - //If URL "/" + roomName is not good, trying with explicitly adding the - //domain name. - if(err && config.hosts.domain) { - pushHistoryState(word, "//" + config.hosts.domain + historyURL); - } + let historyURL = window.location.href + word; + //Trying to push state with current URL + roomName + pushHistoryState(word, historyURL); } return roomName;