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:
commit
06911c4c75
175
conference.js
175
conference.js
|
@ -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();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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":
|
||||
{
|
||||
|
|
175
modules/UI/UI.js
175
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 <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);
|
||||
};
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -36,7 +36,10 @@
|
|||
};
|
||||
navigator.webkitGetUserMedia({
|
||||
audio: false, video: vid_constraint
|
||||
}, callback, errorCallback);
|
||||
}, callback, function (error) {
|
||||
errorCallback &&
|
||||
errorCallback(error, vid_constraint);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue