Merge pull request #676 from jitsi/tsareg-handle_create_local_tracks_errors_better

Tsareg handle create local tracks errors better
This commit is contained in:
lyubomir 2016-06-03 16:17:34 -05:00
commit 06911c4c75
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 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(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);
APP.connection = connection = con;
this._createRoom(tracks);
@ -540,39 +562,78 @@ export default {
}
function createNewTracks(type, cameraDeviceId, micDeviceId) {
return createLocalTracks(type, cameraDeviceId, micDeviceId)
.then(onTracksCreated)
.catch(() => {
// 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);
}
let audioTrackCreationError;
let videoTrackCreationError;
let audioRequested = type.indexOf('audio') !== -1;
let videoRequested = type.indexOf('video') !== -1;
let promise;
})
.then(onTracksCreated)
if (audioRequested && micDeviceId !== null) {
if (videoRequested && cameraDeviceId !== null) {
promise = createLocalTracks(
type, cameraDeviceId, micDeviceId)
.catch(() => {
// 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.
if (type.indexOf('audio') !== -1
&& type.indexOf('video') !== -1) {
return createLocalTracks(['video'],
cameraDeviceId,
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);
}
})
.then(onTracksCreated)
.catch(() => {
// can't do anything in this case, so just ignore;
} 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) {
@ -895,13 +956,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();
@ -976,12 +1037,13 @@ 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
);
@ -989,18 +1051,26 @@ export default {
}
// 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;
let dialogTitle;
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);
});
} else {
createLocalTracks(['video']).then(
@ -1353,10 +1423,15 @@ export default {
APP.UI.addListener(
UIEvents.VIDEO_DEVICE_CHANGED,
(cameraDeviceId) => {
APP.settings.setCameraDeviceId(cameraDeviceId);
createLocalTracks(['video']).then(([stream]) => {
createLocalTracks(['video'])
.then(([stream]) => {
this.useVideoStream(stream);
console.log('switched local video device');
APP.settings.setCameraDeviceId(cameraDeviceId);
})
.catch((err) => {
APP.UI.showDeviceErrorDialog(null, err);
APP.UI.setSelectedCameraFromSettings();
});
}
);
@ -1364,10 +1439,15 @@ export default {
APP.UI.addListener(
UIEvents.AUDIO_DEVICE_CHANGED,
(micDeviceId) => {
APP.settings.setMicDeviceId(micDeviceId);
createLocalTracks(['audio']).then(([stream]) => {
createLocalTracks(['audio'])
.then(([stream]) => {
this.useAudioStream(stream);
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. ' +
'Default or previously set audio output device ' +
'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?",
"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.",
"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":
{

View File

@ -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 <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.
* Currently used by tests (torture).
@ -1106,6 +1154,131 @@ UI.showExtensionRequiredDialog = function (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) {
VideoLayout.setDeviceAvailabilityIcons(id, devices);
};

View File

@ -203,6 +203,28 @@ export default {
$('#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
* no devices available.

View File

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