Merge branch 'handle_create_local_tracks_errors_better' of https://github.com/tsareg/jitsi-meet into tsareg-handle_create_local_tracks_errors_better

This commit is contained in:
Lyubomir Marinov 2016-06-03 14:28:09 -05:00
commit fa1ea94c5c
5 changed files with 353 additions and 60 deletions

View File

@ -374,17 +374,39 @@ export default {
}; };
} }
let audioAndVideoError, audioOnlyError;
return JitsiMeetJS.init(config).then(() => { return JitsiMeetJS.init(config).then(() => {
return Promise.all([ return Promise.all([
// try to retrieve audio and video // try to retrieve audio and video
createLocalTracks(['audio', 'video']) createLocalTracks(['audio', 'video'])
// if failed then try to retrieve only audio // 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 // if audio also failed then just return empty array
.catch(() => []), .catch(err => {
audioOnlyError = err;
return [];
}),
connect(options.roomName) connect(options.roomName)
]); ]);
}).then(([tracks, con]) => { }).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(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(null, audioAndVideoError);
}
}
console.log('initialized with %s local tracks', tracks.length); console.log('initialized with %s local tracks', tracks.length);
APP.connection = connection = con; APP.connection = connection = con;
this._createRoom(tracks); this._createRoom(tracks);
@ -540,39 +562,78 @@ export default {
} }
function createNewTracks(type, cameraDeviceId, micDeviceId) { function createNewTracks(type, cameraDeviceId, micDeviceId) {
return createLocalTracks(type, cameraDeviceId, micDeviceId) let audioTrackCreationError;
.then(onTracksCreated) let videoTrackCreationError;
.catch(() => { let audioRequested = type.indexOf('audio') !== -1;
// if we tried to create both audio and video tracks let videoRequested = type.indexOf('video') !== -1;
// at once and failed, let's try again only with let promise;
// 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);
}
}) if (audioRequested && micDeviceId !== null) {
.then(onTracksCreated) if (videoRequested && cameraDeviceId !== null) {
.catch(() => { promise = createLocalTracks(
// if we tried to create both audio and video tracks type, cameraDeviceId, micDeviceId)
// at once and failed, let's try again only with .catch(() => {
// video. Such situation may happen in case if we return Promise.all([
// granted access only to camera, but not to createAudioTrack(false),
// microphone. createVideoTrack(false)]);
if (type.indexOf('audio') !== -1 })
&& type.indexOf('video') !== -1) { .then((audioTracks, videoTracks) => {
return createLocalTracks(['video'], if (audioTrackCreationError) {
cameraDeviceId, if (videoTrackCreationError) {
null); APP.UI.showDeviceErrorDialog(
} audioTrackCreationError,
}) videoTrackCreationError);
.then(onTracksCreated) } else {
.catch(() => { APP.UI.showDeviceErrorDialog(
// can't do anything in this case, so just ignore; audioTrackCreationError,
}); null);
}
} else if (videoTrackCreationError) {
APP.UI.showDeviceErrorDialog(
null,
videoTrackCreationError);
}
return audioTracks.concat(videoTracks);
});
} else {
promise = createAudioTrack();
}
} else if (videoRequested && cameraDeviceId !== null) {
promise = createVideoTrack();
} else {
promise = Promise.resolve([]);
}
return promise
.then(onTracksCreated);
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) { function onTracksCreated(tracks) {
@ -895,13 +956,13 @@ export default {
this.isSharingScreen = stream.videoType === 'desktop'; this.isSharingScreen = stream.videoType === 'desktop';
APP.UI.addLocalStream(stream); APP.UI.addLocalStream(stream);
stream.videoType === 'camera' && APP.UI.enableCameraButton();
} else { } else {
this.videoMuted = false; this.videoMuted = false;
this.isSharingScreen = false; this.isSharingScreen = false;
} }
stream.videoType === 'camera' && APP.UI.enableCameraButton();
APP.UI.setVideoMuted(this.localId, this.videoMuted); APP.UI.setVideoMuted(this.localId, this.videoMuted);
APP.UI.updateDesktopSharingButtons(); APP.UI.updateDesktopSharingButtons();
@ -976,12 +1037,13 @@ export default {
this.videoSwitchInProgress = false; this.videoSwitchInProgress = false;
this.toggleScreenSharing(false); this.toggleScreenSharing(false);
if(err === TrackErrors.CHROME_EXTENSION_USER_CANCELED) if (err.name === TrackErrors.CHROME_EXTENSION_USER_CANCELED) {
return; return;
}
console.error('failed to share local desktop', err); console.error('failed to share local desktop', err);
if (err === TrackErrors.FIREFOX_EXTENSION_NEEDED) { if (err.name === TrackErrors.FIREFOX_EXTENSION_NEEDED) {
APP.UI.showExtensionRequiredDialog( APP.UI.showExtensionRequiredDialog(
config.desktopSharingFirefoxExtensionURL config.desktopSharingFirefoxExtensionURL
); );
@ -989,18 +1051,26 @@ export default {
} }
// Handling: // Handling:
// TrackErrors.PERMISSION_DENIED
// TrackErrors.CHROME_EXTENSION_INSTALLATION_ERROR // TrackErrors.CHROME_EXTENSION_INSTALLATION_ERROR
// TrackErrors.GENERAL // TrackErrors.GENERAL
// and any other // and any other
let dialogTxt = APP.translation let dialogTxt;
.generateTranslationHTML("dialog.failtoinstall"); let dialogTitle;
let dialogTitle = APP.translation
.generateTranslationHTML("dialog.error"); if (err.name === TrackErrors.PERMISSION_DENIED) {
APP.UI.messageHandler.openDialog( dialogTxt = APP.translation.generateTranslationHTML(
dialogTitle, "dialog.screenSharingPermissionDeniedError");
dialogTxt, dialogTitle = APP.translation.generateTranslationHTML(
false "dialog.error");
); } else {
dialogTxt = APP.translation.generateTranslationHTML(
"dialog.failtoinstall");
dialogTitle = APP.translation.generateTranslationHTML(
"dialog.permissionDenied");
}
APP.UI.messageHandler.openDialog(dialogTitle, dialogTxt, false);
}); });
} else { } else {
createLocalTracks(['video']).then( createLocalTracks(['video']).then(
@ -1353,22 +1423,32 @@ export default {
APP.UI.addListener( APP.UI.addListener(
UIEvents.VIDEO_DEVICE_CHANGED, UIEvents.VIDEO_DEVICE_CHANGED,
(cameraDeviceId) => { (cameraDeviceId) => {
APP.settings.setCameraDeviceId(cameraDeviceId); createLocalTracks(['video'])
createLocalTracks(['video']).then(([stream]) => { .then(([stream]) => {
this.useVideoStream(stream); this.useVideoStream(stream);
console.log('switched local video device'); console.log('switched local video device');
}); APP.settings.setCameraDeviceId(cameraDeviceId);
})
.catch((err) => {
APP.UI.showDeviceErrorDialog(null, err);
APP.UI.setSelectedCameraFromSettings();
});
} }
); );
APP.UI.addListener( APP.UI.addListener(
UIEvents.AUDIO_DEVICE_CHANGED, UIEvents.AUDIO_DEVICE_CHANGED,
(micDeviceId) => { (micDeviceId) => {
APP.settings.setMicDeviceId(micDeviceId); createLocalTracks(['audio'])
createLocalTracks(['audio']).then(([stream]) => { .then(([stream]) => {
this.useAudioStream(stream); this.useAudioStream(stream);
console.log('switched local audio device'); console.log('switched local audio device');
}); APP.settings.setMicDeviceId(micDeviceId);
})
.catch((err) => {
APP.UI.showDeviceErrorDialog(err, null);
APP.UI.setSelectedMicFromSettings();
});
} }
); );
@ -1381,6 +1461,7 @@ export default {
console.warn('Failed to change audio output device. ' + console.warn('Failed to change audio output device. ' +
'Default or previously set audio output device ' + 'Default or previously set audio output device ' +
'will be used instead.', err); 'will be used instead.', err);
APP.UI.setSelectedAudioOutputFromSettings();
}); });
} }
); );

View File

@ -222,7 +222,21 @@
"stopStreamingWarning": "Are you sure you would like to stop the live streaming?", "stopStreamingWarning": "Are you sure you would like to stop the live streaming?",
"stopRecordingWarning": "Are you sure you would like to stop the recording?", "stopRecordingWarning": "Are you sure you would like to stop the recording?",
"stopLiveStreaming": "Stop live streaming", "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.",
"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.",
"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": "email":
{ {

View File

@ -1,4 +1,4 @@
/* global APP, $, config, interfaceConfig, toastr */ /* global APP, JitsiMeetJS, $, config, interfaceConfig, toastr */
/* jshint -W101 */ /* jshint -W101 */
var UI = {}; var UI = {};
@ -38,6 +38,32 @@ let sharedVideoManager;
let followMeHandler; 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. * Prompt user for nickname.
*/ */
@ -1078,6 +1104,28 @@ UI.onAvailableDevicesChanged = function (devices) {
SettingsMenu.changeDevicesList(devices); SettingsMenu.changeDevicesList(devices);
}; };
/**
* Sets microphone's <select> element to select microphone ID from settings.
*/
UI.setSelectedMicFromSettings = function () {
SettingsMenu.setSelectedMicFromSettings();
};
/**
* Sets camera's <select> element to select camera ID from settings.
*/
UI.setSelectedCameraFromSettings = function () {
SettingsMenu.setSelectedCameraFromSettings();
};
/**
* Sets audio outputs's <select> element to select audio output ID from
* settings.
*/
UI.setSelectedAudioOutputFromSettings = function () {
SettingsMenu.setSelectedAudioOutputFromSettings();
};
/** /**
* Returns the id of the current video shown on large. * Returns the id of the current video shown on large.
* Currently used by tests (torture). * Currently used by tests (torture).
@ -1106,6 +1154,131 @@ UI.showExtensionRequiredDialog = function (url) {
"dialog.firefoxExtensionPrompt", {url: url})); "dialog.firefoxExtensionPrompt", {url: url}));
}; };
/**
* Shows dialog with combined information about camera and microphone errors.
* @param {JitsiTrackError} micError
* @param {JitsiTrackError} cameraError
*/
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 (micError) {
localStoragePropName += "-mic-" + micError.name;
}
if (cameraError) {
localStoragePropName += "-camera-" + cameraError.name;
}
if (showDoNotShowWarning) {
if (window.localStorage[localStoragePropName] === "true") {
return;
}
}
let title = getTitleKey();
let titleMsg = `<span data-i18n="${title}"></span>`;
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
? `<div>${cameraError.message}</div>`
: ``;
let additionalMicErrorMsg = !micJitsiTrackErrorMsg && micError &&
micError.message
? `<div>${micError.message}</div>`
: ``;
let doNotShowWarningAgainSection = showDoNotShowWarning
? `<label>
<input type='checkbox' id='doNotShowWarningAgain'>
<span data-i18n='dialog.doNotShowWarningAgain'></span>
</label>`
: ``;
let message = '';
if (micError) {
message = `
${message}
<h3 data-i18n='dialog.micErrorPresent'></h3>
<h4 data-i18n='${micErrorMsg}'></h4>
${additionalMicErrorMsg}`;
}
if (cameraError) {
message = `
${message}
<h3 data-i18n='dialog.cameraErrorPresent'></h3>
<h4 data-i18n='${cameraErrorMsg}'></h4>
${additionalCameraErrorMsg}`;
}
message = `${message}${doNotShowWarningAgainSection}`;
messageHandler.openDialog(
titleMsg,
message,
false,
{Ok: true},
function () {
let form = $.prompt.getPrompt();
if (form) {
let input = form.find("#doNotShowWarningAgain");
if (input.length) {
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) { UI.updateDevicesAvailability = function (id, devices) {
VideoLayout.setDeviceAvailabilityIcons(id, devices); VideoLayout.setDeviceAvailabilityIcons(id, devices);
}; };

View File

@ -203,6 +203,28 @@ export default {
$('#avatar').attr('src', avatarUrl); $('#avatar').attr('src', avatarUrl);
}, },
/**
* Sets microphone's <select> element to select microphone ID from settings.
*/
setSelectedMicFromSettings () {
$('#selectMic').val(Settings.getMicDeviceId());
},
/**
* Sets camera's <select> element to select camera ID from settings.
*/
setSelectedCameraFromSettings () {
$('#selectCamera').val(Settings.getCameraDeviceId());
},
/**
* Sets audio outputs's <select> element to select audio output ID from
* settings.
*/
setSelectedAudioOutputFromSettings () {
$('#selectAudioOutput').val(Settings.getAudioOutputDeviceId());
},
/** /**
* Change available cameras/microphones or hide selects completely if * Change available cameras/microphones or hide selects completely if
* no devices available. * no devices available.

View File

@ -36,7 +36,10 @@
}; };
navigator.webkitGetUserMedia({ navigator.webkitGetUserMedia({
audio: false, video: vid_constraint audio: false, video: vid_constraint
}, callback, errorCallback); }, callback, function (error) {
errorCallback &&
errorCallback(error, vid_constraint);
});
} }
); );
} }