2016-01-06 22:39:13 +00:00
|
|
|
/* global $, APP, JitsiMeetJS, config, interfaceConfig */
|
2016-11-11 15:00:54 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
import { openConnection } from './connection';
|
2016-10-12 00:08:24 +00:00
|
|
|
|
2016-01-06 22:39:13 +00:00
|
|
|
import AuthHandler from './modules/UI/authentication/AuthHandler';
|
2016-05-05 16:14:27 +00:00
|
|
|
import Recorder from './modules/recorder/Recorder';
|
|
|
|
|
2016-06-13 11:49:00 +00:00
|
|
|
import mediaDeviceHelper from './modules/devices/mediaDeviceHelper';
|
|
|
|
|
2017-08-17 16:43:22 +00:00
|
|
|
import * as RemoteControlEvents
|
|
|
|
from './service/remotecontrol/RemoteControlEvents';
|
2016-10-26 19:29:40 +00:00
|
|
|
import UIEvents from './service/UI/UIEvents';
|
2016-09-09 01:19:18 +00:00
|
|
|
import UIUtil from './modules/UI/util/UIUtil';
|
2018-08-31 20:02:04 +00:00
|
|
|
import { createTaskQueue } from './modules/util/helpers';
|
2017-01-23 18:07:08 +00:00
|
|
|
import * as JitsiMeetConferenceEvents from './ConferenceEvents';
|
2016-08-26 16:41:57 +00:00
|
|
|
|
2017-12-11 18:48:32 +00:00
|
|
|
import {
|
2018-01-03 21:24:07 +00:00
|
|
|
createDeviceChangedEvent,
|
|
|
|
createScreenSharingEvent,
|
|
|
|
createStreamSwitchDelayEvent,
|
|
|
|
createTrackMutedEvent,
|
|
|
|
sendAnalytics
|
2017-12-11 18:48:32 +00:00
|
|
|
} from './react/features/analytics';
|
2018-02-26 22:50:27 +00:00
|
|
|
import {
|
|
|
|
redirectWithStoredParams,
|
|
|
|
reloadWithStoredParams
|
|
|
|
} from './react/features/app';
|
2016-11-16 21:02:32 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
import EventEmitter from 'events';
|
2016-12-09 23:15:04 +00:00
|
|
|
|
2017-02-27 21:42:28 +00:00
|
|
|
import {
|
2017-02-28 23:12:02 +00:00
|
|
|
AVATAR_ID_COMMAND,
|
|
|
|
AVATAR_URL_COMMAND,
|
2018-08-16 15:11:06 +00:00
|
|
|
EMAIL_COMMAND,
|
2018-06-20 20:19:53 +00:00
|
|
|
authStatusChanged,
|
2018-08-16 15:11:06 +00:00
|
|
|
commonUserJoinedHandling,
|
|
|
|
commonUserLeftHandling,
|
2017-02-27 21:42:28 +00:00
|
|
|
conferenceFailed,
|
2017-02-28 23:12:02 +00:00
|
|
|
conferenceJoined,
|
|
|
|
conferenceLeft,
|
2019-03-12 17:45:53 +00:00
|
|
|
conferenceSubjectChanged,
|
2017-11-21 22:45:14 +00:00
|
|
|
conferenceWillJoin,
|
2018-08-01 16:41:54 +00:00
|
|
|
conferenceWillLeave,
|
2017-08-09 19:40:03 +00:00
|
|
|
dataChannelOpened,
|
|
|
|
lockStateChanged,
|
2017-11-21 02:21:35 +00:00
|
|
|
onStartMutedPolicyChanged,
|
2017-10-06 17:52:23 +00:00
|
|
|
p2pStatusChanged,
|
2018-03-07 00:28:19 +00:00
|
|
|
sendLocalParticipant,
|
2019-03-12 17:45:53 +00:00
|
|
|
setDesktopSharingEnabled
|
2017-02-27 21:42:28 +00:00
|
|
|
} from './react/features/base/conference';
|
2018-04-12 19:58:20 +00:00
|
|
|
import {
|
2018-08-06 15:24:59 +00:00
|
|
|
getAvailableDevices,
|
2018-04-12 19:58:20 +00:00
|
|
|
setAudioOutputDeviceId,
|
|
|
|
updateDeviceList
|
|
|
|
} from './react/features/base/devices';
|
2017-02-19 00:42:11 +00:00
|
|
|
import {
|
2017-10-10 23:31:40 +00:00
|
|
|
isFatalJitsiConnectionError,
|
|
|
|
JitsiConferenceErrors,
|
|
|
|
JitsiConferenceEvents,
|
|
|
|
JitsiConnectionErrors,
|
|
|
|
JitsiConnectionEvents,
|
|
|
|
JitsiMediaDevicesEvents,
|
|
|
|
JitsiParticipantConnectionStatus,
|
|
|
|
JitsiTrackErrors,
|
|
|
|
JitsiTrackEvents
|
2017-02-19 00:42:11 +00:00
|
|
|
} from './react/features/base/lib-jitsi-meet';
|
2017-07-24 13:56:57 +00:00
|
|
|
import {
|
2017-08-18 11:30:30 +00:00
|
|
|
isVideoMutedByUser,
|
|
|
|
MEDIA_TYPE,
|
2017-07-24 13:56:57 +00:00
|
|
|
setAudioAvailable,
|
2017-08-18 11:30:30 +00:00
|
|
|
setAudioMuted,
|
|
|
|
setVideoAvailable,
|
|
|
|
setVideoMuted
|
2017-07-24 13:56:57 +00:00
|
|
|
} from './react/features/base/media';
|
2017-12-11 18:44:38 +00:00
|
|
|
import { showNotification } from './react/features/notifications';
|
2017-02-27 21:42:28 +00:00
|
|
|
import {
|
2017-09-18 18:35:52 +00:00
|
|
|
dominantSpeakerChanged,
|
2017-12-19 23:11:54 +00:00
|
|
|
getAvatarURLByParticipantId,
|
2017-10-06 17:52:23 +00:00
|
|
|
getLocalParticipant,
|
2019-01-15 11:28:07 +00:00
|
|
|
getNormalizedDisplayName,
|
2017-10-06 17:52:23 +00:00
|
|
|
getParticipantById,
|
2017-07-13 00:26:10 +00:00
|
|
|
localParticipantConnectionStatusChanged,
|
2017-04-10 21:53:30 +00:00
|
|
|
localParticipantRoleChanged,
|
2017-07-13 00:26:10 +00:00
|
|
|
participantConnectionStatusChanged,
|
2017-07-31 23:33:22 +00:00
|
|
|
participantPresenceChanged,
|
2017-03-23 18:01:33 +00:00
|
|
|
participantRoleChanged,
|
|
|
|
participantUpdated
|
2017-02-27 21:42:28 +00:00
|
|
|
} from './react/features/base/participants';
|
2018-04-12 19:58:20 +00:00
|
|
|
import { updateSettings } from './react/features/base/settings';
|
2017-06-20 20:09:34 +00:00
|
|
|
import {
|
2017-08-14 13:25:37 +00:00
|
|
|
createLocalTracksF,
|
2019-01-01 21:19:34 +00:00
|
|
|
destroyLocalTracks,
|
2017-08-18 11:30:30 +00:00
|
|
|
isLocalTrackMuted,
|
2017-06-20 20:09:34 +00:00
|
|
|
replaceLocalTrack,
|
|
|
|
trackAdded,
|
|
|
|
trackRemoved
|
|
|
|
} from './react/features/base/tracks';
|
2017-11-09 20:59:43 +00:00
|
|
|
import {
|
|
|
|
getLocationContextRoot,
|
|
|
|
getJitsiMeetGlobalNS
|
|
|
|
} from './react/features/base/util';
|
2018-08-29 17:24:25 +00:00
|
|
|
import { addMessage } from './react/features/chat';
|
2017-10-12 23:02:29 +00:00
|
|
|
import { showDesktopPicker } from './react/features/desktop-picker';
|
2017-12-05 03:27:17 +00:00
|
|
|
import { appendSuffix } from './react/features/display-name';
|
2018-01-19 22:19:55 +00:00
|
|
|
import {
|
|
|
|
maybeOpenFeedbackDialog,
|
|
|
|
submitFeedback
|
|
|
|
} from './react/features/feedback';
|
2017-03-07 16:50:17 +00:00
|
|
|
import {
|
|
|
|
mediaPermissionPromptVisibilityChanged,
|
|
|
|
suspendDetected
|
|
|
|
} from './react/features/overlay';
|
2018-03-07 00:28:19 +00:00
|
|
|
import { setSharedVideoStatus } from './react/features/shared-video';
|
2018-04-09 05:03:26 +00:00
|
|
|
import { isButtonEnabled } from './react/features/toolbox';
|
2018-07-17 17:31:12 +00:00
|
|
|
import { endpointMessageReceived } from './react/features/subtitles';
|
2017-02-27 21:42:28 +00:00
|
|
|
|
2017-10-16 20:37:13 +00:00
|
|
|
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
|
|
|
|
2017-01-23 18:07:08 +00:00
|
|
|
const eventEmitter = new EventEmitter();
|
|
|
|
|
2017-04-11 19:40:03 +00:00
|
|
|
let room;
|
|
|
|
let connection;
|
2016-07-22 18:42:41 +00:00
|
|
|
|
2017-03-30 16:58:31 +00:00
|
|
|
/*
|
|
|
|
* Logic to open a desktop picker put on the window global for
|
|
|
|
* lib-jitsi-meet to detect and invoke
|
|
|
|
*/
|
|
|
|
window.JitsiMeetScreenObtainer = {
|
2017-07-09 21:34:08 +00:00
|
|
|
openDesktopPicker(options, onSourceChoose) {
|
|
|
|
APP.store.dispatch(showDesktopPicker(options, onSourceChoose));
|
2017-03-30 16:58:31 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-06-13 21:11:44 +00:00
|
|
|
/**
|
|
|
|
* Known custom conference commands.
|
|
|
|
*/
|
|
|
|
const commands = {
|
2017-02-28 23:12:02 +00:00
|
|
|
AVATAR_ID: AVATAR_ID_COMMAND,
|
|
|
|
AVATAR_URL: AVATAR_URL_COMMAND,
|
2017-10-12 23:02:29 +00:00
|
|
|
CUSTOM_ROLE: 'custom-role',
|
2017-02-28 23:12:02 +00:00
|
|
|
EMAIL: EMAIL_COMMAND,
|
2017-10-12 23:02:29 +00:00
|
|
|
ETHERPAD: 'etherpad',
|
|
|
|
SHARED_VIDEO: 'shared-video'
|
2016-06-13 21:11:44 +00:00
|
|
|
};
|
|
|
|
|
2016-01-15 14:59:35 +00:00
|
|
|
/**
|
|
|
|
* Open Connection. When authentication failed it shows auth dialog.
|
2016-02-09 23:44:41 +00:00
|
|
|
* @param roomName the room name to use
|
2016-01-15 14:59:35 +00:00
|
|
|
* @returns Promise<JitsiConnection>
|
|
|
|
*/
|
2016-02-09 23:44:41 +00:00
|
|
|
function connect(roomName) {
|
2017-10-12 23:02:29 +00:00
|
|
|
return openConnection({
|
|
|
|
retry: true,
|
|
|
|
roomName
|
|
|
|
})
|
|
|
|
.catch(err => {
|
|
|
|
if (err === JitsiConnectionErrors.PASSWORD_REQUIRED) {
|
|
|
|
APP.UI.notifyTokenAuthFailed();
|
|
|
|
} else {
|
|
|
|
APP.UI.notifyConnectionFailed(err);
|
|
|
|
}
|
|
|
|
throw err;
|
|
|
|
});
|
2016-01-06 22:39:13 +00:00
|
|
|
}
|
|
|
|
|
2016-01-15 14:59:35 +00:00
|
|
|
/**
|
2016-06-13 21:11:44 +00:00
|
|
|
* Share data to other users.
|
|
|
|
* @param command the command
|
|
|
|
* @param {string} value new value
|
2016-01-15 14:59:35 +00:00
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
function sendData(command, value) {
|
2017-08-18 11:30:30 +00:00
|
|
|
if (!room) {
|
2017-03-23 18:01:33 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-06-13 21:11:44 +00:00
|
|
|
room.removeCommand(command);
|
2017-10-12 23:02:29 +00:00
|
|
|
room.sendCommand(command, { value });
|
2016-01-15 14:59:35 +00:00
|
|
|
}
|
2016-01-06 22:39:13 +00:00
|
|
|
|
2016-01-15 14:59:35 +00:00
|
|
|
/**
|
|
|
|
* Get user nickname by user id.
|
|
|
|
* @param {string} id user id
|
|
|
|
* @returns {string?} user nickname or undefined if user is unknown.
|
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
function getDisplayName(id) {
|
2017-10-06 17:52:23 +00:00
|
|
|
const participant = getParticipantById(APP.store.getState(), id);
|
2016-01-06 22:39:13 +00:00
|
|
|
|
2017-10-06 17:52:23 +00:00
|
|
|
return participant && participant.name;
|
2016-01-15 14:59:35 +00:00
|
|
|
}
|
2016-01-06 22:39:13 +00:00
|
|
|
|
2016-02-03 14:44:10 +00:00
|
|
|
/**
|
|
|
|
* Mute or unmute local audio stream if it exists.
|
2016-10-03 16:12:04 +00:00
|
|
|
* @param {boolean} muted - if audio stream should be muted or unmuted.
|
2016-02-03 14:44:10 +00:00
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
function muteLocalAudio(muted) {
|
2017-08-18 11:30:30 +00:00
|
|
|
APP.store.dispatch(setAudioMuted(muted));
|
2016-02-03 14:44:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Mute or unmute local video stream if it exists.
|
|
|
|
* @param {boolean} muted if video stream should be muted or unmuted.
|
2017-07-24 09:20:32 +00:00
|
|
|
*
|
2016-02-03 14:44:10 +00:00
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
function muteLocalVideo(muted) {
|
2017-08-18 11:30:30 +00:00
|
|
|
APP.store.dispatch(setVideoMuted(muted));
|
2016-02-03 14:44:10 +00:00
|
|
|
}
|
|
|
|
|
2016-05-05 16:11:05 +00:00
|
|
|
/**
|
|
|
|
* Check if the welcome page is enabled and redirects to it.
|
2016-09-22 19:03:01 +00:00
|
|
|
* If requested show a thank you dialog before that.
|
2016-09-22 19:19:50 +00:00
|
|
|
* If we have a close page enabled, redirect to it without
|
|
|
|
* showing any other dialog.
|
2016-11-01 18:14:21 +00:00
|
|
|
*
|
2016-11-01 20:34:21 +00:00
|
|
|
* @param {object} options used to decide which particular close page to show
|
|
|
|
* or if close page is disabled, whether we should show the thankyou dialog
|
2017-12-11 18:44:38 +00:00
|
|
|
* @param {boolean} options.showThankYou - whether we should
|
2016-11-01 19:46:47 +00:00
|
|
|
* show thank you dialog
|
|
|
|
* @param {boolean} options.feedbackSubmitted - whether feedback was submitted
|
2016-05-05 16:11:05 +00:00
|
|
|
*/
|
2016-11-01 19:46:47 +00:00
|
|
|
function maybeRedirectToWelcomePage(options) {
|
2016-09-22 19:19:50 +00:00
|
|
|
// if close page is enabled redirect to it, without further action
|
|
|
|
if (config.enableClosePage) {
|
2017-10-05 22:54:13 +00:00
|
|
|
const { isGuest } = APP.store.getState()['features/base/jwt'];
|
2017-04-21 10:00:50 +00:00
|
|
|
|
2017-01-03 20:15:32 +00:00
|
|
|
// save whether current user is guest or not, before navigating
|
|
|
|
// to close page
|
2017-04-21 10:00:50 +00:00
|
|
|
window.sessionStorage.setItem('guest', isGuest);
|
2018-02-26 22:50:27 +00:00
|
|
|
redirectToStaticPage(`static/${
|
2017-10-12 23:02:29 +00:00
|
|
|
options.feedbackSubmitted ? 'close.html' : 'close2.html'}`);
|
|
|
|
|
2016-09-22 19:19:50 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-11-01 18:14:21 +00:00
|
|
|
// else: show thankYou dialog only if there is no feedback
|
2017-12-11 18:44:38 +00:00
|
|
|
if (options.showThankYou) {
|
|
|
|
APP.store.dispatch(showNotification({
|
|
|
|
titleArguments: { appName: interfaceConfig.APP_NAME },
|
|
|
|
titleKey: 'dialog.thankYou'
|
|
|
|
}));
|
2017-10-12 23:02:29 +00:00
|
|
|
}
|
2016-09-22 19:03:01 +00:00
|
|
|
|
2018-03-28 14:35:26 +00:00
|
|
|
// if Welcome page is enabled redirect to welcome page after 3 sec, if
|
|
|
|
// there is a thank you message to be shown, 0.5s otherwise.
|
2016-11-01 18:14:21 +00:00
|
|
|
if (config.enableWelcomePage) {
|
2017-10-16 20:37:13 +00:00
|
|
|
setTimeout(
|
|
|
|
() => {
|
2018-02-26 22:50:27 +00:00
|
|
|
APP.store.dispatch(redirectWithStoredParams('/'));
|
2017-10-16 20:37:13 +00:00
|
|
|
},
|
2018-03-28 14:35:26 +00:00
|
|
|
options.showThankYou ? 3000 : 500);
|
2016-05-05 16:11:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-05 02:29:24 +00:00
|
|
|
/**
|
|
|
|
* Assigns a specific pathname to window.location.pathname taking into account
|
|
|
|
* the context root of the Web app.
|
|
|
|
*
|
|
|
|
* @param {string} pathname - The pathname to assign to
|
|
|
|
* window.location.pathname. If the specified pathname is relative, the context
|
|
|
|
* root of the Web app will be prepended to the specified pathname before
|
|
|
|
* assigning it to window.location.pathname.
|
|
|
|
* @return {void}
|
|
|
|
*/
|
2018-02-26 22:50:27 +00:00
|
|
|
function redirectToStaticPage(pathname) {
|
2017-02-05 02:29:24 +00:00
|
|
|
const windowLocation = window.location;
|
2017-10-12 23:02:29 +00:00
|
|
|
let newPathname = pathname;
|
2017-02-05 02:29:24 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
if (!newPathname.startsWith('/')) {
|
2017-02-05 02:29:24 +00:00
|
|
|
// A pathname equal to ./ specifies the current directory. It will be
|
|
|
|
// fine but pointless to include it because contextRoot is the current
|
|
|
|
// directory.
|
2017-10-12 23:02:29 +00:00
|
|
|
newPathname.startsWith('./')
|
|
|
|
&& (newPathname = newPathname.substring(2));
|
|
|
|
newPathname = getLocationContextRoot(windowLocation) + newPathname;
|
2017-02-05 02:29:24 +00:00
|
|
|
}
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
windowLocation.pathname = newPathname;
|
2017-02-05 02:29:24 +00:00
|
|
|
}
|
|
|
|
|
2018-08-31 20:02:04 +00:00
|
|
|
/**
|
|
|
|
* A queue for the async replaceLocalTrack action so that multiple audio
|
|
|
|
* replacements cannot happen simultaneously. This solves the issue where
|
|
|
|
* replaceLocalTrack is called multiple times with an oldTrack of null, causing
|
|
|
|
* multiple local tracks of the same type to be used.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @type {Object}
|
|
|
|
*/
|
|
|
|
const _replaceLocalAudioTrackQueue = createTaskQueue();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A task queue for replacement local video tracks. This separate queue exists
|
|
|
|
* so video replacement is not blocked by audio replacement tasks in the queue
|
|
|
|
* {@link _replaceLocalAudioTrackQueue}.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @type {Object}
|
|
|
|
*/
|
|
|
|
const _replaceLocalVideoTrackQueue = createTaskQueue();
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
2016-01-06 22:39:13 +00:00
|
|
|
class ConferenceConnector {
|
2017-10-12 23:02:29 +00:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
2017-04-13 00:23:43 +00:00
|
|
|
constructor(resolve, reject) {
|
2016-01-06 22:39:13 +00:00
|
|
|
this._resolve = resolve;
|
|
|
|
this._reject = reject;
|
|
|
|
this.reconnectTimeout = null;
|
2017-10-10 23:31:40 +00:00
|
|
|
room.on(JitsiConferenceEvents.CONFERENCE_JOINED,
|
2016-01-06 22:39:13 +00:00
|
|
|
this._handleConferenceJoined.bind(this));
|
2017-10-10 23:31:40 +00:00
|
|
|
room.on(JitsiConferenceEvents.CONFERENCE_FAILED,
|
2016-01-06 22:39:13 +00:00
|
|
|
this._onConferenceFailed.bind(this));
|
2017-10-10 23:31:40 +00:00
|
|
|
room.on(JitsiConferenceEvents.CONFERENCE_ERROR,
|
2016-01-25 22:39:05 +00:00
|
|
|
this._onConferenceError.bind(this));
|
2016-01-06 22:39:13 +00:00
|
|
|
}
|
2017-10-12 23:02:29 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
2016-10-03 16:12:04 +00:00
|
|
|
_handleConferenceFailed(err) {
|
2016-01-06 22:39:13 +00:00
|
|
|
this._unsubscribe();
|
|
|
|
this._reject(err);
|
|
|
|
}
|
2017-10-12 23:02:29 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
2016-01-25 22:39:05 +00:00
|
|
|
_onConferenceFailed(err, ...params) {
|
2017-01-31 20:58:48 +00:00
|
|
|
APP.store.dispatch(conferenceFailed(room, err, ...params));
|
2016-11-11 15:00:54 +00:00
|
|
|
logger.error('CONFERENCE FAILED:', err, ...params);
|
2016-01-06 22:39:13 +00:00
|
|
|
|
2017-06-05 18:19:25 +00:00
|
|
|
switch (err) {
|
2017-10-10 23:31:40 +00:00
|
|
|
case JitsiConferenceErrors.CONNECTION_ERROR: {
|
2017-10-12 23:02:29 +00:00
|
|
|
const [ msg ] = params;
|
|
|
|
|
2017-10-02 23:08:07 +00:00
|
|
|
APP.UI.notifyConnectionFailed(msg);
|
2016-01-06 22:39:13 +00:00
|
|
|
break;
|
2017-10-02 23:08:07 +00:00
|
|
|
}
|
2016-01-06 22:39:13 +00:00
|
|
|
|
2017-10-10 23:31:40 +00:00
|
|
|
case JitsiConferenceErrors.NOT_ALLOWED_ERROR: {
|
2017-10-02 23:08:07 +00:00
|
|
|
// let's show some auth not allowed page
|
2018-02-26 22:50:27 +00:00
|
|
|
redirectToStaticPage('static/authError.html');
|
2016-09-27 22:26:38 +00:00
|
|
|
break;
|
2017-10-02 23:08:07 +00:00
|
|
|
}
|
2016-09-27 22:26:38 +00:00
|
|
|
|
2017-10-02 23:08:07 +00:00
|
|
|
// not enough rights to create conference
|
2017-10-10 23:31:40 +00:00
|
|
|
case JitsiConferenceErrors.AUTHENTICATION_REQUIRED: {
|
2017-10-02 23:08:07 +00:00
|
|
|
// Schedule reconnect to check if someone else created the room.
|
2019-01-19 23:53:05 +00:00
|
|
|
this.reconnectTimeout = setTimeout(() => {
|
|
|
|
APP.store.dispatch(conferenceWillJoin(room));
|
|
|
|
room.join();
|
|
|
|
}, 5000);
|
2017-04-13 00:23:43 +00:00
|
|
|
|
2017-10-02 23:08:07 +00:00
|
|
|
const { password }
|
|
|
|
= APP.store.getState()['features/base/conference'];
|
|
|
|
|
|
|
|
AuthHandler.requireAuth(room, password);
|
2017-04-13 00:23:43 +00:00
|
|
|
|
2016-01-06 22:39:13 +00:00
|
|
|
break;
|
2017-10-02 23:08:07 +00:00
|
|
|
}
|
2016-01-06 22:39:13 +00:00
|
|
|
|
2017-10-10 23:31:40 +00:00
|
|
|
case JitsiConferenceErrors.RESERVATION_ERROR: {
|
2017-10-12 23:02:29 +00:00
|
|
|
const [ code, msg ] = params;
|
|
|
|
|
2017-10-02 23:08:07 +00:00
|
|
|
APP.UI.notifyReservationError(code, msg);
|
2016-01-25 22:39:05 +00:00
|
|
|
break;
|
2017-10-02 23:08:07 +00:00
|
|
|
}
|
2016-01-25 22:39:05 +00:00
|
|
|
|
2017-10-10 23:31:40 +00:00
|
|
|
case JitsiConferenceErrors.GRACEFUL_SHUTDOWN:
|
2016-02-05 15:04:48 +00:00
|
|
|
APP.UI.notifyGracefulShutdown();
|
2016-01-25 22:39:05 +00:00
|
|
|
break;
|
|
|
|
|
2017-11-03 19:05:03 +00:00
|
|
|
case JitsiConferenceErrors.JINGLE_FATAL_ERROR: {
|
|
|
|
const [ error ] = params;
|
|
|
|
|
|
|
|
APP.UI.notifyInternalError(error);
|
2016-01-25 22:39:05 +00:00
|
|
|
break;
|
2017-11-03 19:05:03 +00:00
|
|
|
}
|
2016-01-25 22:39:05 +00:00
|
|
|
|
2017-10-10 23:31:40 +00:00
|
|
|
case JitsiConferenceErrors.CONFERENCE_DESTROYED: {
|
2017-10-12 23:02:29 +00:00
|
|
|
const [ reason ] = params;
|
|
|
|
|
2017-10-02 23:08:07 +00:00
|
|
|
APP.UI.hideStats();
|
|
|
|
APP.UI.notifyConferenceDestroyed(reason);
|
2016-01-25 22:39:05 +00:00
|
|
|
break;
|
2017-10-02 23:08:07 +00:00
|
|
|
}
|
2016-01-25 22:39:05 +00:00
|
|
|
|
2017-10-02 23:08:07 +00:00
|
|
|
// FIXME FOCUS_DISCONNECTED is a confusing event name.
|
|
|
|
// What really happens there is that the library is not ready yet,
|
|
|
|
// because Jicofo is not available, but it is going to give it another
|
|
|
|
// try.
|
2017-10-10 23:31:40 +00:00
|
|
|
case JitsiConferenceErrors.FOCUS_DISCONNECTED: {
|
2017-10-12 23:02:29 +00:00
|
|
|
const [ focus, retrySec ] = params;
|
|
|
|
|
2017-10-02 23:08:07 +00:00
|
|
|
APP.UI.notifyFocusDisconnected(focus, retrySec);
|
2016-01-25 22:39:05 +00:00
|
|
|
break;
|
2017-10-02 23:08:07 +00:00
|
|
|
}
|
2016-01-25 22:39:05 +00:00
|
|
|
|
2017-10-10 23:31:40 +00:00
|
|
|
case JitsiConferenceErrors.FOCUS_LEFT:
|
|
|
|
case JitsiConferenceErrors.VIDEOBRIDGE_NOT_AVAILABLE:
|
2018-08-01 20:37:15 +00:00
|
|
|
APP.store.dispatch(conferenceWillLeave(room));
|
|
|
|
|
2016-10-06 18:30:00 +00:00
|
|
|
// FIXME the conference should be stopped by the library and not by
|
|
|
|
// the app. Both the errors above are unrecoverable from the library
|
|
|
|
// perspective.
|
2016-02-22 14:57:36 +00:00
|
|
|
room.leave().then(() => connection.disconnect());
|
2016-02-05 15:04:48 +00:00
|
|
|
break;
|
|
|
|
|
2017-10-10 23:31:40 +00:00
|
|
|
case JitsiConferenceErrors.CONFERENCE_MAX_USERS:
|
2016-03-15 19:08:01 +00:00
|
|
|
connection.disconnect();
|
|
|
|
APP.UI.notifyMaxUsersLimitReached();
|
|
|
|
break;
|
2017-10-02 23:08:07 +00:00
|
|
|
|
2017-10-10 23:31:40 +00:00
|
|
|
case JitsiConferenceErrors.INCOMPATIBLE_SERVER_VERSIONS:
|
2018-02-26 22:50:27 +00:00
|
|
|
APP.store.dispatch(reloadWithStoredParams());
|
2016-07-08 01:44:04 +00:00
|
|
|
break;
|
2017-10-02 23:08:07 +00:00
|
|
|
|
2016-01-06 22:39:13 +00:00
|
|
|
default:
|
2016-01-25 22:39:05 +00:00
|
|
|
this._handleConferenceFailed(err, ...params);
|
|
|
|
}
|
|
|
|
}
|
2017-10-12 23:02:29 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
2016-01-25 22:39:05 +00:00
|
|
|
_onConferenceError(err, ...params) {
|
2016-11-11 15:00:54 +00:00
|
|
|
logger.error('CONFERENCE Error:', err, params);
|
2016-01-25 22:39:05 +00:00
|
|
|
switch (err) {
|
2017-10-10 23:31:40 +00:00
|
|
|
case JitsiConferenceErrors.CHAT_ERROR:
|
2017-10-12 23:02:29 +00:00
|
|
|
logger.error('Chat error.', err);
|
2018-08-29 17:24:25 +00:00
|
|
|
if (isButtonEnabled('chat') && !interfaceConfig.filmStripOnly) {
|
2017-10-12 23:02:29 +00:00
|
|
|
const [ code, msg ] = params;
|
|
|
|
|
2018-08-29 17:24:25 +00:00
|
|
|
APP.store.dispatch(addMessage({
|
|
|
|
hasRead: true,
|
|
|
|
error: code,
|
|
|
|
message: msg,
|
|
|
|
timestamp: Date.now(),
|
|
|
|
type: 'error'
|
|
|
|
}));
|
2016-01-25 22:39:05 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
2017-10-12 23:02:29 +00:00
|
|
|
logger.error('Unknown error.', err);
|
2016-01-06 22:39:13 +00:00
|
|
|
}
|
|
|
|
}
|
2017-10-12 23:02:29 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
2016-01-06 22:39:13 +00:00
|
|
|
_unsubscribe() {
|
|
|
|
room.off(
|
2017-10-12 23:02:29 +00:00
|
|
|
JitsiConferenceEvents.CONFERENCE_JOINED,
|
|
|
|
this._handleConferenceJoined);
|
2016-01-06 22:39:13 +00:00
|
|
|
room.off(
|
2017-10-12 23:02:29 +00:00
|
|
|
JitsiConferenceEvents.CONFERENCE_FAILED,
|
|
|
|
this._onConferenceFailed);
|
2016-01-06 22:39:13 +00:00
|
|
|
if (this.reconnectTimeout !== null) {
|
|
|
|
clearTimeout(this.reconnectTimeout);
|
|
|
|
}
|
|
|
|
AuthHandler.closeAuth();
|
|
|
|
}
|
2017-10-12 23:02:29 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
2016-01-06 22:39:13 +00:00
|
|
|
_handleConferenceJoined() {
|
|
|
|
this._unsubscribe();
|
|
|
|
this._resolve();
|
|
|
|
}
|
2017-10-12 23:02:29 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
2016-01-06 22:39:13 +00:00
|
|
|
connect() {
|
|
|
|
room.join();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-12 21:30:44 +00:00
|
|
|
/**
|
|
|
|
* Disconnects the connection.
|
|
|
|
* @returns resolved Promise. We need this in order to make the Promise.all
|
|
|
|
* call in hangup() to resolve when all operations are finished.
|
|
|
|
*/
|
|
|
|
function disconnect() {
|
|
|
|
connection.disconnect();
|
|
|
|
APP.API.notifyConferenceLeft(APP.conference.roomName);
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-10-12 21:30:44 +00:00
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
|
2017-01-31 20:58:48 +00:00
|
|
|
/**
|
|
|
|
* Handles CONNECTION_FAILED events from lib-jitsi-meet.
|
2017-02-19 00:42:11 +00:00
|
|
|
*
|
2017-10-10 23:31:40 +00:00
|
|
|
* @param {JitsiConnectionError} error - The reported error.
|
2017-01-31 20:58:48 +00:00
|
|
|
* @returns {void}
|
|
|
|
* @private
|
|
|
|
*/
|
2017-02-19 00:42:11 +00:00
|
|
|
function _connectionFailedHandler(error) {
|
|
|
|
if (isFatalJitsiConnectionError(error)) {
|
|
|
|
APP.connection.removeEventListener(
|
2017-10-10 23:31:40 +00:00
|
|
|
JitsiConnectionEvents.CONNECTION_FAILED,
|
2017-01-31 20:58:48 +00:00
|
|
|
_connectionFailedHandler);
|
2017-10-12 23:02:29 +00:00
|
|
|
if (room) {
|
2018-08-01 20:37:15 +00:00
|
|
|
APP.store.dispatch(conferenceWillLeave(room));
|
2017-01-31 20:58:48 +00:00
|
|
|
room.leave();
|
2017-10-12 23:02:29 +00:00
|
|
|
}
|
2017-01-31 20:58:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-06 22:39:13 +00:00
|
|
|
export default {
|
2017-07-21 09:12:33 +00:00
|
|
|
/**
|
|
|
|
* Flag used to delay modification of the muted status of local media tracks
|
|
|
|
* until those are created (or not, but at that point it's certain that
|
|
|
|
* the tracks won't exist).
|
|
|
|
*/
|
|
|
|
_localTracksInitialized: false,
|
2016-01-06 22:39:13 +00:00
|
|
|
isModerator: false,
|
2016-02-04 15:25:11 +00:00
|
|
|
isSharingScreen: false,
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2017-07-14 09:25:28 +00:00
|
|
|
/**
|
|
|
|
* Indicates if the desktop sharing functionality has been enabled.
|
2018-06-15 18:10:22 +00:00
|
|
|
* It takes into consideration the status returned by
|
2017-07-14 09:25:28 +00:00
|
|
|
* {@link JitsiMeetJS.isDesktopSharingEnabled()}. The latter can be false
|
|
|
|
* either if the desktop sharing is not supported by the current browser
|
|
|
|
* or if it was disabled through lib-jitsi-meet specific options (check
|
|
|
|
* config.js for listed options).
|
|
|
|
*/
|
2016-02-04 15:25:11 +00:00
|
|
|
isDesktopSharingEnabled: false,
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2017-08-18 11:30:30 +00:00
|
|
|
/**
|
|
|
|
* The local audio track (if any).
|
|
|
|
* FIXME tracks from redux store should be the single source of truth
|
|
|
|
* @type {JitsiLocalTrack|null}
|
|
|
|
*/
|
|
|
|
localAudio: null,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The local video track (if any).
|
|
|
|
* FIXME tracks from redux store should be the single source of truth, but
|
|
|
|
* more refactoring is required around screen sharing ('localVideo' usages).
|
|
|
|
* @type {JitsiLocalTrack|null}
|
|
|
|
*/
|
|
|
|
localVideo: null,
|
|
|
|
|
2017-07-17 11:38:46 +00:00
|
|
|
/**
|
|
|
|
* Creates local media tracks and connects to a room. Will show error
|
|
|
|
* dialogs in case accessing the local microphone and/or camera failed. Will
|
|
|
|
* show guidance overlay for users on how to give access to camera and/or
|
2017-07-11 13:06:58 +00:00
|
|
|
* microphone.
|
2017-07-17 11:38:46 +00:00
|
|
|
* @param {string} roomName
|
2017-07-18 10:25:37 +00:00
|
|
|
* @param {object} options
|
2017-07-11 13:06:58 +00:00
|
|
|
* @param {boolean} options.startAudioOnly=false - if <tt>true</tt> then
|
|
|
|
* only audio track will be created and the audio only mode will be turned
|
|
|
|
* on.
|
|
|
|
* @param {boolean} options.startScreenSharing=false - if <tt>true</tt>
|
|
|
|
* should start with screensharing instead of camera video.
|
2017-07-24 16:10:31 +00:00
|
|
|
* @param {boolean} options.startWithAudioMuted - will start the conference
|
|
|
|
* without any audio tracks.
|
|
|
|
* @param {boolean} options.startWithVideoMuted - will start the conference
|
|
|
|
* without any video tracks.
|
2017-07-17 11:38:46 +00:00
|
|
|
* @returns {Promise.<JitsiLocalTrack[], JitsiConnection>}
|
|
|
|
*/
|
2017-07-18 10:25:37 +00:00
|
|
|
createInitialLocalTracksAndConnect(roomName, options = {}) {
|
2017-07-17 11:38:46 +00:00
|
|
|
let audioAndVideoError,
|
|
|
|
audioOnlyError,
|
2017-06-29 17:43:35 +00:00
|
|
|
screenSharingError,
|
2017-07-17 11:38:46 +00:00
|
|
|
videoOnlyError;
|
2017-07-24 16:10:31 +00:00
|
|
|
const initialDevices = [];
|
|
|
|
let requestedAudio = false;
|
|
|
|
let requestedVideo = false;
|
|
|
|
|
|
|
|
if (!options.startWithAudioMuted) {
|
|
|
|
initialDevices.push('audio');
|
|
|
|
requestedAudio = true;
|
|
|
|
}
|
|
|
|
if (!options.startWithVideoMuted
|
|
|
|
&& !options.startAudioOnly
|
|
|
|
&& !options.startScreenSharing) {
|
|
|
|
initialDevices.push('video');
|
|
|
|
requestedVideo = true;
|
|
|
|
}
|
2017-07-17 11:38:46 +00:00
|
|
|
|
|
|
|
JitsiMeetJS.mediaDevices.addEventListener(
|
2017-10-10 23:31:40 +00:00
|
|
|
JitsiMediaDevicesEvents.PERMISSION_PROMPT_IS_SHOWN,
|
2017-07-17 11:38:46 +00:00
|
|
|
browser =>
|
|
|
|
APP.store.dispatch(
|
|
|
|
mediaPermissionPromptVisibilityChanged(true, browser))
|
|
|
|
);
|
|
|
|
|
2017-06-29 17:43:35 +00:00
|
|
|
let tryCreateLocalTracks;
|
|
|
|
|
2017-07-24 16:10:31 +00:00
|
|
|
// FIXME is there any simpler way to rewrite this spaghetti below ?
|
|
|
|
if (options.startScreenSharing) {
|
2017-06-29 17:43:35 +00:00
|
|
|
tryCreateLocalTracks = this._createDesktopTrack()
|
|
|
|
.then(desktopStream => {
|
2017-07-24 16:10:31 +00:00
|
|
|
if (!requestedAudio) {
|
2017-10-12 23:02:29 +00:00
|
|
|
return [ desktopStream ];
|
2017-07-24 16:10:31 +00:00
|
|
|
}
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
return createLocalTracksF({ devices: [ 'audio' ] }, true)
|
|
|
|
.then(([ audioStream ]) =>
|
|
|
|
[ desktopStream, audioStream ])
|
2017-06-29 17:43:35 +00:00
|
|
|
.catch(error => {
|
|
|
|
audioOnlyError = error;
|
2017-10-12 23:02:29 +00:00
|
|
|
|
|
|
|
return [ desktopStream ];
|
2017-06-29 17:43:35 +00:00
|
|
|
});
|
2017-10-12 23:02:29 +00:00
|
|
|
})
|
|
|
|
.catch(error => {
|
2017-06-29 17:43:35 +00:00
|
|
|
logger.error('Failed to obtain desktop stream', error);
|
|
|
|
screenSharingError = error;
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2017-07-24 16:10:31 +00:00
|
|
|
return requestedAudio
|
2017-10-12 23:02:29 +00:00
|
|
|
? createLocalTracksF({ devices: [ 'audio' ] }, true)
|
2017-07-24 16:10:31 +00:00
|
|
|
: [];
|
2017-10-12 23:02:29 +00:00
|
|
|
})
|
|
|
|
.catch(error => {
|
2017-06-29 17:43:35 +00:00
|
|
|
audioOnlyError = error;
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2017-06-29 17:43:35 +00:00
|
|
|
return [];
|
|
|
|
});
|
2017-07-24 16:10:31 +00:00
|
|
|
} else if (!requestedAudio && !requestedVideo) {
|
|
|
|
// Resolve with no tracks
|
|
|
|
tryCreateLocalTracks = Promise.resolve([]);
|
2017-06-29 17:43:35 +00:00
|
|
|
} else {
|
2017-08-14 13:25:37 +00:00
|
|
|
tryCreateLocalTracks = createLocalTracksF(
|
2017-07-24 16:10:31 +00:00
|
|
|
{ devices: initialDevices }, true)
|
2017-06-29 17:43:35 +00:00
|
|
|
.catch(err => {
|
2017-07-24 16:10:31 +00:00
|
|
|
if (requestedAudio && requestedVideo) {
|
|
|
|
|
|
|
|
// Try audio only...
|
|
|
|
audioAndVideoError = err;
|
|
|
|
|
2017-10-16 20:37:13 +00:00
|
|
|
return (
|
|
|
|
createLocalTracksF({ devices: [ 'audio' ] }, true));
|
2017-07-24 16:10:31 +00:00
|
|
|
} else if (requestedAudio && !requestedVideo) {
|
|
|
|
audioOnlyError = err;
|
|
|
|
|
|
|
|
return [];
|
|
|
|
} else if (requestedVideo && !requestedAudio) {
|
|
|
|
videoOnlyError = err;
|
|
|
|
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
logger.error('Should never happen');
|
2017-10-12 23:02:29 +00:00
|
|
|
})
|
|
|
|
.catch(err => {
|
2017-07-24 16:10:31 +00:00
|
|
|
// Log this just in case...
|
|
|
|
if (!requestedAudio) {
|
|
|
|
logger.error('The impossible just happened', err);
|
|
|
|
}
|
2017-06-29 17:43:35 +00:00
|
|
|
audioOnlyError = err;
|
|
|
|
|
|
|
|
// Try video only...
|
2017-07-24 16:10:31 +00:00
|
|
|
return requestedVideo
|
2017-10-12 23:02:29 +00:00
|
|
|
? createLocalTracksF({ devices: [ 'video' ] }, true)
|
2017-07-24 16:10:31 +00:00
|
|
|
: [];
|
2017-06-29 17:43:35 +00:00
|
|
|
})
|
|
|
|
.catch(err => {
|
2017-07-24 16:10:31 +00:00
|
|
|
// Log this just in case...
|
|
|
|
if (!requestedVideo) {
|
|
|
|
logger.error('The impossible just happened', err);
|
|
|
|
}
|
2017-06-29 17:43:35 +00:00
|
|
|
videoOnlyError = err;
|
|
|
|
|
|
|
|
return [];
|
|
|
|
});
|
|
|
|
}
|
2017-07-17 11:38:46 +00:00
|
|
|
|
2017-11-30 04:47:24 +00:00
|
|
|
// Hide the permissions prompt/overlay as soon as the tracks are
|
|
|
|
// created. Don't wait for the connection to be made, since in some
|
|
|
|
// cases, when auth is rquired, for instance, that won't happen until
|
|
|
|
// the user inputs their credentials, but the dialog would be
|
|
|
|
// overshadowed by the overlay.
|
|
|
|
tryCreateLocalTracks.then(() =>
|
|
|
|
APP.store.dispatch(mediaPermissionPromptVisibilityChanged(false)));
|
2017-11-28 11:02:51 +00:00
|
|
|
|
2017-07-17 11:38:46 +00:00
|
|
|
return Promise.all([ tryCreateLocalTracks, connect(roomName) ])
|
2017-10-12 23:02:29 +00:00
|
|
|
.then(([ tracks, con ]) => {
|
2017-06-29 18:49:00 +00:00
|
|
|
// FIXME If there will be microphone error it will cover any
|
|
|
|
// screensharing dialog, but it's still better than in
|
|
|
|
// the reverse order where the screensharing dialog will
|
2017-07-18 10:28:27 +00:00
|
|
|
// sometimes be closing the microphone alert ($.prompt.close();
|
2017-06-29 18:49:00 +00:00
|
|
|
// is called). Need to figure out dialogs chaining to fix that.
|
|
|
|
if (screenSharingError) {
|
|
|
|
this._handleScreenSharingError(screenSharingError);
|
|
|
|
}
|
2017-06-29 17:43:35 +00:00
|
|
|
if (audioAndVideoError || audioOnlyError) {
|
|
|
|
if (audioOnlyError || videoOnlyError) {
|
2017-07-17 11:38:46 +00:00
|
|
|
// If both requests for 'audio' + 'video' and 'audio'
|
2017-07-18 10:28:27 +00:00
|
|
|
// only failed, we assume that there are some problems
|
2017-07-17 11:38:46 +00:00
|
|
|
// with user's microphone and show corresponding dialog.
|
2017-07-31 18:36:41 +00:00
|
|
|
APP.UI.showMicErrorNotification(audioOnlyError);
|
|
|
|
APP.UI.showCameraErrorNotification(videoOnlyError);
|
2017-07-17 11:38:46 +00:00
|
|
|
} 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.
|
2017-07-31 18:36:41 +00:00
|
|
|
APP.UI.showCameraErrorNotification(audioAndVideoError);
|
2017-07-17 11:38:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
return [ tracks, con ];
|
2017-07-17 11:38:46 +00:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2016-01-15 14:59:35 +00:00
|
|
|
/**
|
|
|
|
* Open new connection and join to the conference.
|
|
|
|
* @param {object} options
|
2018-04-16 12:44:08 +00:00
|
|
|
* @param {string} roomName - The name of the conference.
|
2016-01-15 14:59:35 +00:00
|
|
|
* @returns {Promise}
|
|
|
|
*/
|
2016-01-06 22:39:13 +00:00
|
|
|
init(options) {
|
|
|
|
this.roomName = options.roomName;
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2017-10-02 23:08:07 +00:00
|
|
|
return (
|
2018-04-16 12:44:08 +00:00
|
|
|
this.createInitialLocalTracksAndConnect(
|
|
|
|
options.roomName, {
|
|
|
|
startAudioOnly: config.startAudioOnly,
|
|
|
|
startScreenSharing: config.startScreenSharing,
|
|
|
|
startWithAudioMuted: config.startWithAudioMuted,
|
|
|
|
startWithVideoMuted: config.startWithVideoMuted
|
|
|
|
})
|
2017-10-12 23:02:29 +00:00
|
|
|
.then(([ tracks, con ]) => {
|
2017-04-04 20:55:03 +00:00
|
|
|
tracks.forEach(track => {
|
2018-01-03 21:24:07 +00:00
|
|
|
if ((track.isAudioTrack() && this.isLocalAudioMuted())
|
|
|
|
|| (track.isVideoTrack() && this.isLocalVideoMuted())) {
|
|
|
|
const mediaType = track.getType();
|
|
|
|
|
|
|
|
sendAnalytics(
|
|
|
|
createTrackMutedEvent(mediaType, 'initial mute'));
|
|
|
|
logger.log(`${mediaType} mute: initially muted.`);
|
2017-04-04 20:55:03 +00:00
|
|
|
track.mute();
|
|
|
|
}
|
|
|
|
});
|
2018-07-23 18:03:49 +00:00
|
|
|
logger.log(`initialized with ${tracks.length} local tracks`);
|
2017-07-21 09:12:33 +00:00
|
|
|
this._localTracksInitialized = true;
|
2017-01-31 20:58:48 +00:00
|
|
|
con.addEventListener(
|
2017-10-10 23:31:40 +00:00
|
|
|
JitsiConnectionEvents.CONNECTION_FAILED,
|
2017-01-31 20:58:48 +00:00
|
|
|
_connectionFailedHandler);
|
2016-06-23 08:03:26 +00:00
|
|
|
APP.connection = connection = con;
|
2017-07-14 09:25:28 +00:00
|
|
|
|
|
|
|
// Desktop sharing related stuff:
|
|
|
|
this.isDesktopSharingEnabled
|
2018-06-15 18:10:22 +00:00
|
|
|
= JitsiMeetJS.isDesktopSharingEnabled();
|
2017-04-06 22:09:55 +00:00
|
|
|
eventEmitter.emit(
|
|
|
|
JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
|
|
|
|
this.isDesktopSharingEnabled);
|
2017-02-16 23:02:40 +00:00
|
|
|
|
2018-03-07 00:28:19 +00:00
|
|
|
APP.store.dispatch(
|
|
|
|
setDesktopSharingEnabled(this.isDesktopSharingEnabled));
|
2017-02-16 23:02:40 +00:00
|
|
|
|
2016-06-23 08:03:26 +00:00
|
|
|
this._createRoom(tracks);
|
2017-04-17 19:59:24 +00:00
|
|
|
APP.remoteControl.init();
|
2016-06-23 08:03:26 +00:00
|
|
|
|
|
|
|
// if user didn't give access to mic or camera or doesn't have
|
2017-07-24 13:56:57 +00:00
|
|
|
// them at all, we mark corresponding toolbar buttons as muted,
|
|
|
|
// so that the user can try unmute later on and add audio/video
|
|
|
|
// to the conference
|
2017-10-12 23:02:29 +00:00
|
|
|
if (!tracks.find(t => t.isAudioTrack())) {
|
2017-08-04 08:15:11 +00:00
|
|
|
this.setAudioMuteStatus(true);
|
2016-06-21 09:08:32 +00:00
|
|
|
}
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
if (!tracks.find(t => t.isVideoTrack())) {
|
2017-08-04 08:15:11 +00:00
|
|
|
this.setVideoMuteStatus(true);
|
2016-05-26 08:53:02 +00:00
|
|
|
}
|
|
|
|
|
2016-06-23 08:03:26 +00:00
|
|
|
this._initDeviceList();
|
2016-05-17 15:58:25 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
if (config.iAmRecorder) {
|
2016-06-23 08:03:26 +00:00
|
|
|
this.recorder = new Recorder();
|
2017-10-12 23:02:29 +00:00
|
|
|
}
|
2016-05-17 15:58:25 +00:00
|
|
|
|
2016-06-23 08:03:26 +00:00
|
|
|
// XXX The API will take care of disconnecting from the XMPP
|
|
|
|
// server (and, thus, leaving the room) on unload.
|
|
|
|
return new Promise((resolve, reject) => {
|
2017-04-13 00:23:43 +00:00
|
|
|
(new ConferenceConnector(resolve, reject)).connect();
|
2016-06-23 08:03:26 +00:00
|
|
|
});
|
2017-10-12 23:02:29 +00:00
|
|
|
})
|
|
|
|
);
|
2016-01-06 22:39:13 +00:00
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-01-15 14:59:35 +00:00
|
|
|
/**
|
|
|
|
* Check if id is id of the local user.
|
|
|
|
* @param {string} id id to check
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
isLocalId(id) {
|
2016-07-08 01:44:04 +00:00
|
|
|
return this.getMyUserId() === id;
|
2016-01-06 22:39:13 +00:00
|
|
|
},
|
2017-08-18 11:30:30 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Tells whether the local video is muted or not.
|
|
|
|
* @return {boolean}
|
|
|
|
*/
|
|
|
|
isLocalVideoMuted() {
|
|
|
|
// If the tracks are not ready, read from base/media state
|
|
|
|
return this._localTracksInitialized
|
|
|
|
? isLocalTrackMuted(
|
|
|
|
APP.store.getState()['features/base/tracks'],
|
|
|
|
MEDIA_TYPE.VIDEO)
|
|
|
|
: isVideoMutedByUser(APP.store);
|
|
|
|
},
|
|
|
|
|
2016-01-06 22:39:13 +00:00
|
|
|
/**
|
|
|
|
* Simulates toolbar button click for audio mute. Used by shortcuts and API.
|
2017-07-25 10:05:08 +00:00
|
|
|
* @param {boolean} mute true for mute and false for unmute.
|
|
|
|
* @param {boolean} [showUI] when set to false will not display any error
|
|
|
|
* dialogs in case of media permissions error.
|
2016-01-06 22:39:13 +00:00
|
|
|
*/
|
2017-07-25 10:05:08 +00:00
|
|
|
muteAudio(mute, showUI = true) {
|
|
|
|
// Not ready to modify track's state yet
|
|
|
|
if (!this._localTracksInitialized) {
|
2017-08-18 11:30:30 +00:00
|
|
|
// This will only modify base/media.audio.muted which is then synced
|
|
|
|
// up with the track at the end of local tracks initialization.
|
|
|
|
muteLocalAudio(mute);
|
2017-08-04 08:15:11 +00:00
|
|
|
this.setAudioMuteStatus(mute);
|
2017-08-18 11:30:30 +00:00
|
|
|
|
2017-07-25 10:05:08 +00:00
|
|
|
return;
|
2017-08-18 11:30:30 +00:00
|
|
|
} else if (this.isLocalAudioMuted() === mute) {
|
2017-07-25 10:05:08 +00:00
|
|
|
// NO-OP
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-08-18 11:30:30 +00:00
|
|
|
if (!this.localAudio && !mute) {
|
2017-08-14 13:25:37 +00:00
|
|
|
const maybeShowErrorDialog = error => {
|
|
|
|
showUI && APP.UI.showMicErrorNotification(error);
|
|
|
|
};
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
createLocalTracksF({ devices: [ 'audio' ] }, false)
|
|
|
|
.then(([ audioTrack ]) => audioTrack)
|
2017-07-25 10:05:08 +00:00
|
|
|
.catch(error => {
|
2017-08-14 13:25:37 +00:00
|
|
|
maybeShowErrorDialog(error);
|
2017-07-25 10:05:08 +00:00
|
|
|
|
|
|
|
// Rollback the audio muted status by using null track
|
|
|
|
return null;
|
|
|
|
})
|
|
|
|
.then(audioTrack => this.useAudioStream(audioTrack));
|
|
|
|
} else {
|
2017-08-18 11:30:30 +00:00
|
|
|
muteLocalAudio(mute);
|
2017-07-25 10:05:08 +00:00
|
|
|
}
|
2016-01-06 22:39:13 +00:00
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-02-09 16:29:50 +00:00
|
|
|
/**
|
|
|
|
* Returns whether local audio is muted or not.
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
|
|
|
isLocalAudioMuted() {
|
2017-08-18 11:30:30 +00:00
|
|
|
// If the tracks are not ready, read from base/media state
|
|
|
|
return this._localTracksInitialized
|
|
|
|
? isLocalTrackMuted(
|
|
|
|
APP.store.getState()['features/base/tracks'],
|
|
|
|
MEDIA_TYPE.AUDIO)
|
|
|
|
: Boolean(
|
|
|
|
APP.store.getState()['features/base/media'].audio.muted);
|
2016-02-09 16:29:50 +00:00
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-01-06 22:39:13 +00:00
|
|
|
/**
|
2017-04-04 20:55:03 +00:00
|
|
|
* Simulates toolbar button click for audio mute. Used by shortcuts
|
|
|
|
* and API.
|
2017-07-25 10:05:08 +00:00
|
|
|
* @param {boolean} [showUI] when set to false will not display any error
|
|
|
|
* dialogs in case of media permissions error.
|
2016-01-06 22:39:13 +00:00
|
|
|
*/
|
2017-07-25 10:05:08 +00:00
|
|
|
toggleAudioMuted(showUI = true) {
|
2017-08-18 11:30:30 +00:00
|
|
|
this.muteAudio(!this.isLocalAudioMuted(), showUI);
|
2016-01-06 22:39:13 +00:00
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-01-06 22:39:13 +00:00
|
|
|
/**
|
|
|
|
* Simulates toolbar button click for video mute. Used by shortcuts and API.
|
|
|
|
* @param mute true for mute and false for unmute.
|
2017-07-20 12:29:15 +00:00
|
|
|
* @param {boolean} [showUI] when set to false will not display any error
|
|
|
|
* dialogs in case of media permissions error.
|
2016-01-06 22:39:13 +00:00
|
|
|
*/
|
2017-07-20 12:29:15 +00:00
|
|
|
muteVideo(mute, showUI = true) {
|
2017-08-18 11:30:30 +00:00
|
|
|
// If not ready to modify track's state yet adjust the base/media
|
2017-07-21 09:12:33 +00:00
|
|
|
if (!this._localTracksInitialized) {
|
2017-08-18 11:30:30 +00:00
|
|
|
// This will only modify base/media.video.muted which is then synced
|
|
|
|
// up with the track at the end of local tracks initialization.
|
|
|
|
muteLocalVideo(mute);
|
2017-08-04 08:15:11 +00:00
|
|
|
this.setVideoMuteStatus(mute);
|
2017-07-21 09:12:33 +00:00
|
|
|
|
2017-07-24 15:36:19 +00:00
|
|
|
return;
|
2017-08-18 11:30:30 +00:00
|
|
|
} else if (this.isLocalVideoMuted() === mute) {
|
2017-07-24 15:36:19 +00:00
|
|
|
// NO-OP
|
2017-07-21 09:12:33 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-08-18 11:30:30 +00:00
|
|
|
if (!this.localVideo && !mute) {
|
2017-08-14 13:25:37 +00:00
|
|
|
const maybeShowErrorDialog = error => {
|
|
|
|
showUI && APP.UI.showCameraErrorNotification(error);
|
|
|
|
};
|
|
|
|
|
2017-07-20 12:29:15 +00:00
|
|
|
// Try to create local video if there wasn't any.
|
|
|
|
// This handles the case when user joined with no video
|
|
|
|
// (dismissed screen sharing screen or in audio only mode), but
|
|
|
|
// decided to add it later on by clicking on muted video icon or
|
|
|
|
// turning off the audio only mode.
|
|
|
|
//
|
|
|
|
// FIXME when local track creation is moved to react/redux
|
|
|
|
// it should take care of the use case described above
|
2017-10-12 23:02:29 +00:00
|
|
|
createLocalTracksF({ devices: [ 'video' ] }, false)
|
|
|
|
.then(([ videoTrack ]) => videoTrack)
|
2017-07-20 12:29:15 +00:00
|
|
|
.catch(error => {
|
|
|
|
// FIXME should send some feedback to the API on error ?
|
2017-07-24 09:20:32 +00:00
|
|
|
maybeShowErrorDialog(error);
|
|
|
|
|
2017-07-20 12:29:15 +00:00
|
|
|
// Rollback the video muted status by using null track
|
|
|
|
return null;
|
|
|
|
})
|
|
|
|
.then(videoTrack => this.useVideoStream(videoTrack));
|
|
|
|
} else {
|
2017-08-18 11:30:30 +00:00
|
|
|
// FIXME show error dialog if it fails (should be handled by react)
|
|
|
|
muteLocalVideo(mute);
|
2017-07-20 12:29:15 +00:00
|
|
|
}
|
2016-01-06 22:39:13 +00:00
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-01-06 22:39:13 +00:00
|
|
|
/**
|
|
|
|
* Simulates toolbar button click for video mute. Used by shortcuts and API.
|
2017-07-20 12:29:15 +00:00
|
|
|
* @param {boolean} [showUI] when set to false will not display any error
|
|
|
|
* dialogs in case of media permissions error.
|
2016-01-06 22:39:13 +00:00
|
|
|
*/
|
2017-07-20 12:29:15 +00:00
|
|
|
toggleVideoMuted(showUI = true) {
|
2017-08-18 11:30:30 +00:00
|
|
|
this.muteVideo(!this.isLocalVideoMuted(), showUI);
|
2016-01-06 22:39:13 +00:00
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-01-15 14:59:35 +00:00
|
|
|
/**
|
|
|
|
* Retrieve list of conference participants (without local user).
|
|
|
|
* @returns {JitsiParticipant[]}
|
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
listMembers() {
|
2016-01-06 22:39:13 +00:00
|
|
|
return room.getParticipants();
|
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-01-15 14:59:35 +00:00
|
|
|
/**
|
|
|
|
* Retrieve list of ids of conference participants (without local user).
|
|
|
|
* @returns {string[]}
|
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
listMembersIds() {
|
2016-01-06 22:39:13 +00:00
|
|
|
return room.getParticipants().map(p => p.getId());
|
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-03-24 20:25:26 +00:00
|
|
|
/**
|
|
|
|
* Checks whether the participant identified by id is a moderator.
|
|
|
|
* @id id to search for participant
|
|
|
|
* @return {boolean} whether the participant is moderator
|
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
isParticipantModerator(id) {
|
2017-10-12 23:02:29 +00:00
|
|
|
const user = room.getParticipantById(id);
|
|
|
|
|
2016-03-24 20:25:26 +00:00
|
|
|
return user && user.isModerator();
|
|
|
|
},
|
2017-10-16 20:37:13 +00:00
|
|
|
|
2017-04-11 19:40:03 +00:00
|
|
|
get membersCount() {
|
2015-12-31 15:23:23 +00:00
|
|
|
return room.getParticipants().length + 1;
|
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-01-20 21:41:37 +00:00
|
|
|
/**
|
|
|
|
* Returns true if the callstats integration is enabled, otherwise returns
|
|
|
|
* false.
|
|
|
|
*
|
|
|
|
* @returns true if the callstats integration is enabled, otherwise returns
|
|
|
|
* false.
|
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
isCallstatsEnabled() {
|
2017-02-06 13:32:05 +00:00
|
|
|
return room && room.isCallstatsEnabled();
|
2016-01-20 21:41:37 +00:00
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-01-20 21:41:37 +00:00
|
|
|
/**
|
|
|
|
* Sends the given feedback through CallStats if enabled.
|
|
|
|
*
|
|
|
|
* @param overallFeedback an integer between 1 and 5 indicating the
|
|
|
|
* user feedback
|
|
|
|
* @param detailedFeedback detailed feedback from the user. Not yet used
|
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
sendFeedback(overallFeedback, detailedFeedback) {
|
2017-10-12 23:02:29 +00:00
|
|
|
return room.sendFeedback(overallFeedback, detailedFeedback);
|
2016-01-20 21:41:37 +00:00
|
|
|
},
|
2017-02-17 00:59:30 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get speaker stats that track total dominant speaker time.
|
|
|
|
*
|
|
|
|
* @returns {object} A hash with keys being user ids and values being the
|
|
|
|
* library's SpeakerStats model used for calculating time as dominant
|
|
|
|
* speaker.
|
|
|
|
*/
|
|
|
|
getSpeakerStats() {
|
|
|
|
return room.getSpeakerStats();
|
|
|
|
},
|
|
|
|
|
2016-08-03 16:08:23 +00:00
|
|
|
/**
|
|
|
|
* Returns the connection times stored in the library.
|
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
getConnectionTimes() {
|
2016-08-03 16:08:23 +00:00
|
|
|
return this._room.getConnectionTimes();
|
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-01-06 22:39:13 +00:00
|
|
|
// used by torture currently
|
2017-04-11 19:40:03 +00:00
|
|
|
isJoined() {
|
2016-01-06 22:39:13 +00:00
|
|
|
return this._room
|
|
|
|
&& this._room.isJoined();
|
|
|
|
},
|
2017-04-11 19:40:03 +00:00
|
|
|
getConnectionState() {
|
2016-01-06 22:39:13 +00:00
|
|
|
return this._room
|
|
|
|
&& this._room.getConnectionState();
|
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2017-02-15 21:19:52 +00:00
|
|
|
/**
|
|
|
|
* Obtains current P2P ICE connection state.
|
|
|
|
* @return {string|null} ICE connection state or <tt>null</tt> if there's no
|
|
|
|
* P2P connection
|
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
getP2PConnectionState() {
|
2017-02-15 21:19:52 +00:00
|
|
|
return this._room
|
|
|
|
&& this._room.getP2PConnectionState();
|
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2017-02-15 21:19:52 +00:00
|
|
|
/**
|
|
|
|
* Starts P2P (for tests only)
|
|
|
|
* @private
|
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
_startP2P() {
|
2017-02-15 21:19:52 +00:00
|
|
|
try {
|
|
|
|
this._room && this._room.startP2PSession();
|
|
|
|
} catch (error) {
|
2017-10-12 23:02:29 +00:00
|
|
|
logger.error('Start P2P failed', error);
|
2017-02-15 21:19:52 +00:00
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2017-02-15 21:19:52 +00:00
|
|
|
/**
|
|
|
|
* Stops P2P (for tests only)
|
|
|
|
* @private
|
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
_stopP2P() {
|
2017-02-15 21:19:52 +00:00
|
|
|
try {
|
|
|
|
this._room && this._room.stopP2PSession();
|
|
|
|
} catch (error) {
|
2017-10-12 23:02:29 +00:00
|
|
|
logger.error('Stop P2P failed', error);
|
2017-02-15 21:19:52 +00:00
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-07-28 16:09:22 +00:00
|
|
|
/**
|
|
|
|
* Checks whether or not our connection is currently in interrupted and
|
|
|
|
* reconnect attempts are in progress.
|
|
|
|
*
|
|
|
|
* @returns {boolean} true if the connection is in interrupted state or
|
|
|
|
* false otherwise.
|
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
isConnectionInterrupted() {
|
2016-10-27 18:25:01 +00:00
|
|
|
return this._room.isConnectionInterrupted();
|
2016-07-28 16:09:22 +00:00
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2017-10-06 17:52:23 +00:00
|
|
|
/**
|
|
|
|
* Obtains the local display name.
|
|
|
|
* @returns {string|undefined}
|
|
|
|
*/
|
|
|
|
getLocalDisplayName() {
|
|
|
|
return getDisplayName(this.getMyUserId());
|
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-09-16 20:40:24 +00:00
|
|
|
/**
|
|
|
|
* Finds JitsiParticipant for given id.
|
|
|
|
*
|
|
|
|
* @param {string} id participant's identifier(MUC nickname).
|
|
|
|
*
|
|
|
|
* @returns {JitsiParticipant|null} participant instance for given id or
|
|
|
|
* null if not found.
|
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
getParticipantById(id) {
|
2016-09-16 20:40:24 +00:00
|
|
|
return room ? room.getParticipantById(id) : null;
|
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-09-16 20:40:24 +00:00
|
|
|
/**
|
2017-04-03 16:53:04 +00:00
|
|
|
* Get participant connection status for the participant.
|
2016-09-16 20:40:24 +00:00
|
|
|
*
|
|
|
|
* @param {string} id participant's identifier(MUC nickname)
|
|
|
|
*
|
2017-04-03 16:53:04 +00:00
|
|
|
* @returns {ParticipantConnectionStatus|null} the status of the participant
|
|
|
|
* or null if no such participant is found or participant is the local user.
|
2016-09-16 20:40:24 +00:00
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
getParticipantConnectionStatus(id) {
|
2017-10-12 23:02:29 +00:00
|
|
|
const participant = this.getParticipantById(id);
|
|
|
|
|
2017-04-11 19:40:03 +00:00
|
|
|
return participant ? participant.getConnectionStatus() : null;
|
2016-09-16 20:40:24 +00:00
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-09-24 16:46:19 +00:00
|
|
|
/**
|
|
|
|
* Gets the display name foe the <tt>JitsiParticipant</tt> identified by
|
|
|
|
* the given <tt>id</tt>.
|
|
|
|
*
|
|
|
|
* @param id {string} the participant's id(MUC nickname/JVB endpoint id)
|
|
|
|
*
|
|
|
|
* @return {string} the participant's display name or the default string if
|
|
|
|
* absent.
|
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
getParticipantDisplayName(id) {
|
2017-10-12 23:02:29 +00:00
|
|
|
const displayName = getDisplayName(id);
|
|
|
|
|
2016-09-24 16:46:19 +00:00
|
|
|
if (displayName) {
|
|
|
|
return displayName;
|
2017-10-12 23:02:29 +00:00
|
|
|
}
|
|
|
|
if (APP.conference.isLocalId(id)) {
|
|
|
|
return APP.translation.generateTranslationHTML(
|
2016-09-24 16:46:19 +00:00
|
|
|
interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME);
|
|
|
|
}
|
2017-10-12 23:02:29 +00:00
|
|
|
|
|
|
|
return interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
|
2016-09-24 16:46:19 +00:00
|
|
|
},
|
2017-10-16 20:37:13 +00:00
|
|
|
|
2017-04-11 19:40:03 +00:00
|
|
|
getMyUserId() {
|
2017-10-16 20:37:13 +00:00
|
|
|
return this._room && this._room.myUserId();
|
2016-01-06 22:39:13 +00:00
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-01-19 19:32:29 +00:00
|
|
|
/**
|
|
|
|
* Will be filled with values only when config.debug is enabled.
|
|
|
|
* Its used by torture to check audio levels.
|
|
|
|
*/
|
|
|
|
audioLevelsMap: {},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-02-02 20:54:15 +00:00
|
|
|
/**
|
|
|
|
* Returns the stored audio level (stored only if config.debug is enabled)
|
|
|
|
* @param id the id for the user audio level to return (the id value is
|
|
|
|
* returned for the participant using getMyUserId() method)
|
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
getPeerSSRCAudioLevel(id) {
|
2016-01-19 19:32:29 +00:00
|
|
|
return this.audioLevelsMap[id];
|
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-01-14 19:50:10 +00:00
|
|
|
/**
|
2016-04-21 22:48:30 +00:00
|
|
|
* @return {number} the number of participants in the conference with at
|
|
|
|
* least one track.
|
2016-01-14 19:50:10 +00:00
|
|
|
*/
|
2016-04-21 22:48:30 +00:00
|
|
|
getNumberOfParticipantsWithTracks() {
|
|
|
|
return this._room.getParticipants()
|
2017-10-12 23:02:29 +00:00
|
|
|
.filter(p => p.getTracks().length > 0)
|
2016-04-21 22:48:30 +00:00
|
|
|
.length;
|
2016-01-14 19:50:10 +00:00
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-02-04 18:41:23 +00:00
|
|
|
/**
|
|
|
|
* Returns the stats.
|
|
|
|
*/
|
|
|
|
getStats() {
|
2016-10-26 19:29:40 +00:00
|
|
|
return room.connectionQuality.getStats();
|
2016-02-04 18:41:23 +00:00
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-01-14 19:50:10 +00:00
|
|
|
// end used by torture
|
|
|
|
|
2017-04-11 19:40:03 +00:00
|
|
|
getLogs() {
|
2016-01-14 15:35:37 +00:00
|
|
|
return room.getLogs();
|
|
|
|
},
|
2016-03-11 10:49:13 +00:00
|
|
|
|
2016-09-21 20:46:10 +00:00
|
|
|
/**
|
|
|
|
* Download logs, a function that can be called from console while
|
|
|
|
* debugging.
|
|
|
|
* @param filename (optional) specify target filename
|
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
saveLogs(filename = 'meetlog.json') {
|
2016-09-21 20:46:10 +00:00
|
|
|
// this can be called from console and will not have reference to this
|
|
|
|
// that's why we reference the global var
|
2017-10-12 23:02:29 +00:00
|
|
|
const logs = APP.conference.getLogs();
|
|
|
|
const data = encodeURIComponent(JSON.stringify(logs, null, ' '));
|
2016-09-21 20:46:10 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
const elem = document.createElement('a');
|
2016-09-21 20:46:10 +00:00
|
|
|
|
|
|
|
elem.download = filename;
|
2017-10-12 23:02:29 +00:00
|
|
|
elem.href = `data:application/json;charset=utf-8,\n${data}`;
|
2016-09-21 20:46:10 +00:00
|
|
|
elem.dataset.downloadurl
|
2017-10-12 23:02:29 +00:00
|
|
|
= [ 'text/json', elem.download, elem.href ].join(':');
|
2016-09-21 20:46:10 +00:00
|
|
|
elem.dispatchEvent(new MouseEvent('click', {
|
|
|
|
view: window,
|
|
|
|
bubbles: true,
|
|
|
|
cancelable: false
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
|
2016-03-11 10:49:13 +00:00
|
|
|
/**
|
|
|
|
* Exposes a Command(s) API on this instance. It is necessitated by (1) the
|
|
|
|
* desire to keep room private to this instance and (2) the need of other
|
|
|
|
* modules to send and receive commands to and from participants.
|
|
|
|
* Eventually, this instance remains in control with respect to the
|
|
|
|
* decision whether the Command(s) API of room (i.e. lib-jitsi-meet's
|
|
|
|
* JitsiConference) is to be used in the implementation of the Command(s)
|
|
|
|
* API of this instance.
|
|
|
|
*/
|
|
|
|
commands: {
|
2016-04-07 17:08:00 +00:00
|
|
|
/**
|
|
|
|
* Known custom conference commands.
|
|
|
|
*/
|
2016-06-13 21:11:44 +00:00
|
|
|
defaults: commands,
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-03-11 10:49:13 +00:00
|
|
|
/**
|
|
|
|
* Receives notifications from other participants about commands aka
|
|
|
|
* custom events (sent by sendCommand or sendCommandOnce methods).
|
|
|
|
* @param command {String} the name of the command
|
|
|
|
* @param handler {Function} handler for the command
|
|
|
|
*/
|
2017-10-12 23:02:29 +00:00
|
|
|
addCommandListener() {
|
|
|
|
// eslint-disable-next-line prefer-rest-params
|
|
|
|
room.addCommandListener(...arguments);
|
2016-03-11 10:49:13 +00:00
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-03-11 10:49:13 +00:00
|
|
|
/**
|
|
|
|
* Removes command.
|
|
|
|
* @param name {String} the name of the command.
|
|
|
|
*/
|
2017-10-12 23:02:29 +00:00
|
|
|
removeCommand() {
|
|
|
|
// eslint-disable-next-line prefer-rest-params
|
|
|
|
room.removeCommand(...arguments);
|
2016-03-11 10:49:13 +00:00
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-03-11 10:49:13 +00:00
|
|
|
/**
|
|
|
|
* Sends command.
|
|
|
|
* @param name {String} the name of the command.
|
|
|
|
* @param values {Object} with keys and values that will be sent.
|
|
|
|
*/
|
2017-10-12 23:02:29 +00:00
|
|
|
sendCommand() {
|
|
|
|
// eslint-disable-next-line prefer-rest-params
|
|
|
|
room.sendCommand(...arguments);
|
2016-03-11 10:49:13 +00:00
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-03-11 10:49:13 +00:00
|
|
|
/**
|
|
|
|
* Sends command one time.
|
|
|
|
* @param name {String} the name of the command.
|
|
|
|
* @param values {Object} with keys and values that will be sent.
|
|
|
|
*/
|
2017-10-12 23:02:29 +00:00
|
|
|
sendCommandOnce() {
|
|
|
|
// eslint-disable-next-line prefer-rest-params
|
|
|
|
room.sendCommandOnce(...arguments);
|
2016-04-07 17:08:00 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-10-13 19:31:05 +00:00
|
|
|
_createRoom(localTracks) {
|
|
|
|
room
|
|
|
|
= connection.initJitsiConference(
|
|
|
|
APP.conference.roomName,
|
|
|
|
this._getConferenceOptions());
|
2019-02-07 03:19:02 +00:00
|
|
|
|
2017-11-21 22:45:14 +00:00
|
|
|
APP.store.dispatch(conferenceWillJoin(room));
|
2016-06-13 11:49:00 +00:00
|
|
|
this._setLocalAudioVideoStreams(localTracks);
|
2016-04-07 17:08:00 +00:00
|
|
|
this._room = room; // FIXME do not use this
|
|
|
|
|
2017-10-13 19:31:05 +00:00
|
|
|
sendLocalParticipant(APP.store, room);
|
2016-04-07 17:08:00 +00:00
|
|
|
|
|
|
|
this._setupListeners();
|
2016-03-11 10:49:13 +00:00
|
|
|
},
|
|
|
|
|
2016-06-13 11:49:00 +00:00
|
|
|
/**
|
|
|
|
* Sets local video and audio streams.
|
|
|
|
* @param {JitsiLocalTrack[]} tracks=[]
|
|
|
|
* @returns {Promise[]}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_setLocalAudioVideoStreams(tracks = []) {
|
|
|
|
return tracks.map(track => {
|
|
|
|
if (track.isAudioTrack()) {
|
|
|
|
return this.useAudioStream(track);
|
|
|
|
} else if (track.isVideoTrack()) {
|
|
|
|
return this.useVideoStream(track);
|
|
|
|
}
|
2017-10-12 23:02:29 +00:00
|
|
|
logger.error(
|
|
|
|
'Ignored not an audio nor a video track: ', track);
|
|
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
2016-06-13 11:49:00 +00:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2016-01-06 22:39:13 +00:00
|
|
|
_getConferenceOptions() {
|
2017-10-12 23:02:29 +00:00
|
|
|
const options = config;
|
|
|
|
|
2018-04-12 19:58:20 +00:00
|
|
|
const nick = APP.store.getState()['features/base/settings'].displayName;
|
2017-10-05 18:28:19 +00:00
|
|
|
|
|
|
|
if (nick) {
|
|
|
|
options.displayName = nick;
|
|
|
|
}
|
|
|
|
|
2017-10-13 20:34:27 +00:00
|
|
|
options.applicationName = interfaceConfig.APP_NAME;
|
2017-11-09 20:59:43 +00:00
|
|
|
options.getWiFiStatsMethod = getJitsiMeetGlobalNS().getWiFiStats;
|
2017-10-13 20:34:27 +00:00
|
|
|
|
2016-01-06 22:39:13 +00:00
|
|
|
return options;
|
|
|
|
},
|
2016-02-04 15:25:11 +00:00
|
|
|
|
2017-04-11 19:40:03 +00:00
|
|
|
/**
|
|
|
|
* Start using provided video stream.
|
|
|
|
* Stops previous video stream.
|
|
|
|
* @param {JitsiLocalTrack} [stream] new stream to use or null
|
|
|
|
* @returns {Promise}
|
|
|
|
*/
|
|
|
|
useVideoStream(newStream) {
|
2018-08-31 20:02:04 +00:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
_replaceLocalVideoTrackQueue.enqueue(onFinish => {
|
|
|
|
APP.store.dispatch(
|
|
|
|
replaceLocalTrack(this.localVideo, newStream, room))
|
|
|
|
.then(() => {
|
|
|
|
this.localVideo = newStream;
|
|
|
|
this._setSharingScreen(newStream);
|
|
|
|
if (newStream) {
|
|
|
|
APP.UI.addLocalStream(newStream);
|
|
|
|
}
|
|
|
|
this.setVideoMuteStatus(this.isLocalVideoMuted());
|
|
|
|
})
|
|
|
|
.then(resolve)
|
|
|
|
.catch(reject)
|
|
|
|
.then(onFinish);
|
2017-01-19 18:46:11 +00:00
|
|
|
});
|
2018-08-31 20:02:04 +00:00
|
|
|
});
|
2016-02-09 10:19:43 +00:00
|
|
|
},
|
|
|
|
|
2018-01-30 13:43:06 +00:00
|
|
|
/**
|
|
|
|
* Sets `this.isSharingScreen` depending on provided video stream.
|
|
|
|
* In case new screen sharing status is not equal previous one
|
|
|
|
* it updates desktop sharing buttons in UI
|
|
|
|
* and notifies external application.
|
|
|
|
*
|
|
|
|
* @param {JitsiLocalTrack} [newStream] new stream to use or null
|
|
|
|
* @private
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
_setSharingScreen(newStream) {
|
|
|
|
const wasSharingScreen = this.isSharingScreen;
|
|
|
|
|
|
|
|
this.isSharingScreen = newStream && newStream.videoType === 'desktop';
|
|
|
|
|
|
|
|
if (wasSharingScreen !== this.isSharingScreen) {
|
2019-03-07 05:46:17 +00:00
|
|
|
const details = {};
|
|
|
|
|
|
|
|
if (this.isSharingScreen) {
|
|
|
|
details.sourceType = newStream.sourceType;
|
|
|
|
}
|
|
|
|
|
|
|
|
APP.API.notifyScreenSharingStatusChanged(
|
|
|
|
this.isSharingScreen, details);
|
2018-01-30 13:43:06 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2016-02-09 10:19:43 +00:00
|
|
|
/**
|
|
|
|
* Start using provided audio stream.
|
|
|
|
* Stops previous audio stream.
|
|
|
|
* @param {JitsiLocalTrack} [stream] new stream to use or null
|
2016-02-16 13:33:53 +00:00
|
|
|
* @returns {Promise}
|
2016-02-09 10:19:43 +00:00
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
useAudioStream(newStream) {
|
2018-08-31 20:02:04 +00:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
_replaceLocalAudioTrackQueue.enqueue(onFinish => {
|
|
|
|
APP.store.dispatch(
|
|
|
|
replaceLocalTrack(this.localAudio, newStream, room))
|
|
|
|
.then(() => {
|
|
|
|
this.localAudio = newStream;
|
|
|
|
if (newStream) {
|
|
|
|
APP.UI.addLocalStream(newStream);
|
|
|
|
}
|
|
|
|
this.setAudioMuteStatus(this.isLocalAudioMuted());
|
|
|
|
})
|
|
|
|
.then(resolve)
|
|
|
|
.catch(reject)
|
|
|
|
.then(onFinish);
|
2017-01-19 18:46:11 +00:00
|
|
|
});
|
2018-08-31 20:02:04 +00:00
|
|
|
});
|
2016-02-09 10:19:43 +00:00
|
|
|
},
|
|
|
|
|
2017-04-05 15:14:26 +00:00
|
|
|
/**
|
|
|
|
* Returns whether or not the conference is currently in audio only mode.
|
|
|
|
*
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
|
|
|
isAudioOnly() {
|
|
|
|
return Boolean(
|
|
|
|
APP.store.getState()['features/base/conference'].audioOnly);
|
|
|
|
},
|
2017-03-29 17:43:30 +00:00
|
|
|
|
2016-02-04 15:25:11 +00:00
|
|
|
videoSwitchInProgress: false,
|
2017-06-13 19:24:34 +00:00
|
|
|
|
2017-06-20 14:52:44 +00:00
|
|
|
/**
|
|
|
|
* This fields stores a handler which will create a Promise which turns off
|
|
|
|
* the screen sharing and restores the previous video state (was there
|
|
|
|
* any video, before switching to screen sharing ? was it muted ?).
|
|
|
|
*
|
|
|
|
* Once called this fields is cleared to <tt>null</tt>.
|
|
|
|
* @type {Function|null}
|
|
|
|
*/
|
|
|
|
_untoggleScreenSharing: null,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a Promise which turns off the screen sharing and restores
|
|
|
|
* the previous state described by the arguments.
|
|
|
|
*
|
|
|
|
* This method is bound to the appropriate values, after switching to screen
|
|
|
|
* sharing and stored in {@link _untoggleScreenSharing}.
|
|
|
|
*
|
|
|
|
* @param {boolean} didHaveVideo indicates if there was a camera video being
|
|
|
|
* used, before switching to screen sharing.
|
|
|
|
* @param {boolean} wasVideoMuted indicates if the video was muted, before
|
|
|
|
* switching to screen sharing.
|
|
|
|
* @return {Promise} resolved after the screen sharing is turned off, or
|
|
|
|
* rejected with some error (no idea what kind of error, possible GUM error)
|
|
|
|
* in case it fails.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_turnScreenSharingOff(didHaveVideo, wasVideoMuted) {
|
|
|
|
this._untoggleScreenSharing = null;
|
|
|
|
this.videoSwitchInProgress = true;
|
2017-10-20 00:38:55 +00:00
|
|
|
const { receiver } = APP.remoteControl;
|
|
|
|
|
|
|
|
if (receiver) {
|
|
|
|
receiver.stop();
|
|
|
|
}
|
|
|
|
|
2019-01-26 20:53:11 +00:00
|
|
|
this._stopProxyConnection();
|
|
|
|
|
2017-06-20 14:52:44 +00:00
|
|
|
let promise = null;
|
|
|
|
|
|
|
|
if (didHaveVideo) {
|
2017-10-12 23:02:29 +00:00
|
|
|
promise = createLocalTracksF({ devices: [ 'video' ] })
|
|
|
|
.then(([ stream ]) => this.useVideoStream(stream))
|
2017-06-20 14:52:44 +00:00
|
|
|
.then(() => {
|
2018-01-03 21:24:07 +00:00
|
|
|
sendAnalytics(createScreenSharingEvent('stopped'));
|
|
|
|
logger.log('Screen sharing stopped, switching to video.');
|
|
|
|
|
2017-08-18 11:30:30 +00:00
|
|
|
if (!this.localVideo && wasVideoMuted) {
|
2017-06-20 14:52:44 +00:00
|
|
|
return Promise.reject('No local video to be muted!');
|
2017-08-18 11:30:30 +00:00
|
|
|
} else if (wasVideoMuted && this.localVideo) {
|
|
|
|
return this.localVideo.mute();
|
2017-06-20 14:52:44 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch(error => {
|
|
|
|
logger.error('failed to switch back to local video', error);
|
2017-10-12 23:02:29 +00:00
|
|
|
|
|
|
|
return this.useVideoStream(null).then(() =>
|
|
|
|
|
2017-06-20 14:52:44 +00:00
|
|
|
// Still fail with the original err
|
2017-10-12 23:02:29 +00:00
|
|
|
Promise.reject(error)
|
|
|
|
);
|
2017-06-20 14:52:44 +00:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
promise = this.useVideoStream(null);
|
|
|
|
}
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2017-06-20 14:52:44 +00:00
|
|
|
return promise.then(
|
|
|
|
() => {
|
|
|
|
this.videoSwitchInProgress = false;
|
|
|
|
},
|
|
|
|
error => {
|
|
|
|
this.videoSwitchInProgress = false;
|
|
|
|
throw error;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2017-06-13 19:24:34 +00:00
|
|
|
/**
|
2017-08-07 08:23:05 +00:00
|
|
|
* Toggles between screen sharing and camera video if the toggle parameter
|
|
|
|
* is not specified and starts the procedure for obtaining new screen
|
|
|
|
* sharing/video track otherwise.
|
|
|
|
*
|
|
|
|
* @param {boolean} [toggle] - If true - new screen sharing track will be
|
|
|
|
* obtained. If false - new video track will be obtain. If not specified -
|
|
|
|
* toggles between screen sharing and camera video.
|
2017-07-09 21:34:08 +00:00
|
|
|
* @param {Object} [options] - Screen sharing options that will be passed to
|
|
|
|
* createLocalTracks.
|
|
|
|
* @param {Array<string>} [options.desktopSharingSources] - Array with the
|
|
|
|
* sources that have to be displayed in the desktop picker window ('screen',
|
|
|
|
* 'window', etc.).
|
2017-06-13 19:24:34 +00:00
|
|
|
* @return {Promise.<T>}
|
|
|
|
*/
|
2017-08-07 08:23:05 +00:00
|
|
|
toggleScreenSharing(toggle = !this._untoggleScreenSharing, options = {}) {
|
2016-02-04 15:25:11 +00:00
|
|
|
if (this.videoSwitchInProgress) {
|
2017-06-13 19:24:34 +00:00
|
|
|
return Promise.reject('Switch in progress.');
|
2016-02-04 15:25:11 +00:00
|
|
|
}
|
|
|
|
if (!this.isDesktopSharingEnabled) {
|
2017-06-13 19:24:34 +00:00
|
|
|
return Promise.reject(
|
|
|
|
'Cannot toggle screen sharing: not supported.');
|
2016-02-04 15:25:11 +00:00
|
|
|
}
|
|
|
|
|
2017-04-05 15:14:26 +00:00
|
|
|
if (this.isAudioOnly()) {
|
2017-06-13 19:24:34 +00:00
|
|
|
return Promise.reject('No screensharing in audio only mode');
|
2017-04-05 15:14:26 +00:00
|
|
|
}
|
|
|
|
|
2017-08-07 08:23:05 +00:00
|
|
|
if (toggle) {
|
2017-06-29 17:43:35 +00:00
|
|
|
return this._switchToScreenSharing(options);
|
|
|
|
}
|
2017-10-12 23:02:29 +00:00
|
|
|
|
|
|
|
return this._untoggleScreenSharing();
|
2017-06-29 17:43:35 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates desktop (screensharing) {@link JitsiLocalTrack}
|
2019-01-26 20:53:11 +00:00
|
|
|
*
|
2017-06-29 17:43:35 +00:00
|
|
|
* @param {Object} [options] - Screen sharing options that will be passed to
|
|
|
|
* createLocalTracks.
|
2019-01-26 20:53:11 +00:00
|
|
|
* @param {Object} [options.desktopSharing]
|
|
|
|
* @param {Object} [options.desktopStream] - An existing desktop stream to
|
|
|
|
* use instead of creating a new desktop stream.
|
2017-06-29 17:43:35 +00:00
|
|
|
* @return {Promise.<JitsiLocalTrack>} - A Promise resolved with
|
|
|
|
* {@link JitsiLocalTrack} for the screensharing or rejected with
|
|
|
|
* {@link JitsiTrackError}.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_createDesktopTrack(options = {}) {
|
|
|
|
let externalInstallation = false;
|
|
|
|
let DSExternalInstallationInProgress = false;
|
2017-08-18 11:30:30 +00:00
|
|
|
const didHaveVideo = Boolean(this.localVideo);
|
|
|
|
const wasVideoMuted = this.isLocalVideoMuted();
|
2017-06-29 17:43:35 +00:00
|
|
|
|
2019-01-26 20:53:11 +00:00
|
|
|
const getDesktopStreamPromise = options.desktopStream
|
|
|
|
? Promise.resolve([ options.desktopStream ])
|
|
|
|
: createLocalTracksF({
|
|
|
|
desktopSharingSourceDevice: options.desktopSharingSources
|
|
|
|
? null : config._desktopSharingSourceDevice,
|
|
|
|
desktopSharingSources: options.desktopSharingSources,
|
|
|
|
devices: [ 'desktop' ],
|
|
|
|
desktopSharingExtensionExternalInstallation: {
|
|
|
|
interval: 500,
|
|
|
|
checkAgain: () => DSExternalInstallationInProgress,
|
|
|
|
listener: (status, url) => {
|
|
|
|
switch (status) {
|
|
|
|
case 'waitingForExtension': {
|
|
|
|
DSExternalInstallationInProgress = true;
|
|
|
|
externalInstallation = true;
|
|
|
|
const listener = () => {
|
|
|
|
// Wait a little bit more just to be sure that
|
|
|
|
// we won't miss the extension installation
|
|
|
|
setTimeout(() => {
|
2017-06-29 17:43:35 +00:00
|
|
|
DSExternalInstallationInProgress = false;
|
2017-07-18 21:43:58 +00:00
|
|
|
},
|
|
|
|
500);
|
2019-01-26 20:53:11 +00:00
|
|
|
APP.UI.removeListener(
|
|
|
|
UIEvents.EXTERNAL_INSTALLATION_CANCELED,
|
|
|
|
listener);
|
|
|
|
};
|
|
|
|
|
|
|
|
APP.UI.addListener(
|
2017-06-29 17:43:35 +00:00
|
|
|
UIEvents.EXTERNAL_INSTALLATION_CANCELED,
|
|
|
|
listener);
|
2019-01-26 20:53:11 +00:00
|
|
|
APP.UI.showExtensionExternalInstallationDialog(url);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'extensionFound':
|
|
|
|
// Close the dialog.
|
|
|
|
externalInstallation && $.prompt.close();
|
|
|
|
break;
|
|
|
|
default:
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2019-01-26 20:53:11 +00:00
|
|
|
// Unknown status
|
|
|
|
}
|
2016-08-10 19:13:32 +00:00
|
|
|
}
|
|
|
|
}
|
2019-01-26 20:53:11 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
return getDesktopStreamPromise.then(([ desktopStream ]) => {
|
2017-06-29 17:43:35 +00:00
|
|
|
// Stores the "untoggle" handler which remembers whether was
|
|
|
|
// there any video before and whether was it muted.
|
|
|
|
this._untoggleScreenSharing
|
|
|
|
= this._turnScreenSharingOff
|
|
|
|
.bind(this, didHaveVideo, wasVideoMuted);
|
|
|
|
desktopStream.on(
|
2017-10-10 23:31:40 +00:00
|
|
|
JitsiTrackEvents.LOCAL_TRACK_STOPPED,
|
2017-06-29 17:43:35 +00:00
|
|
|
() => {
|
|
|
|
// If the stream was stopped during screen sharing
|
|
|
|
// session then we should switch back to video.
|
2017-07-18 21:43:58 +00:00
|
|
|
this.isSharingScreen
|
|
|
|
&& this._untoggleScreenSharing
|
|
|
|
&& this._untoggleScreenSharing();
|
2016-05-25 12:04:48 +00:00
|
|
|
}
|
2017-06-29 17:43:35 +00:00
|
|
|
);
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2017-06-29 17:43:35 +00:00
|
|
|
// close external installation dialog on success.
|
2017-07-18 21:43:58 +00:00
|
|
|
externalInstallation && $.prompt.close();
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2017-06-29 17:43:35 +00:00
|
|
|
return desktopStream;
|
|
|
|
}, error => {
|
|
|
|
DSExternalInstallationInProgress = false;
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2017-06-29 17:43:35 +00:00
|
|
|
// close external installation dialog on success.
|
2017-07-18 21:43:58 +00:00
|
|
|
externalInstallation && $.prompt.close();
|
2017-06-29 17:43:35 +00:00
|
|
|
throw error;
|
|
|
|
});
|
|
|
|
},
|
2016-03-04 21:21:07 +00:00
|
|
|
|
2017-06-29 17:43:35 +00:00
|
|
|
/**
|
2018-01-03 21:24:07 +00:00
|
|
|
* Tries to switch to the screensharing mode by disposing camera stream and
|
2017-06-29 17:43:35 +00:00
|
|
|
* replacing it with a desktop one.
|
|
|
|
*
|
|
|
|
* @param {Object} [options] - Screen sharing options that will be passed to
|
|
|
|
* createLocalTracks.
|
|
|
|
*
|
|
|
|
* @return {Promise} - A Promise resolved if the operation succeeds or
|
|
|
|
* rejected with some unknown type of error in case it fails. Promise will
|
|
|
|
* be rejected immediately if {@link videoSwitchInProgress} is true.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_switchToScreenSharing(options = {}) {
|
|
|
|
if (this.videoSwitchInProgress) {
|
|
|
|
return Promise.reject('Switch in progress.');
|
|
|
|
}
|
2017-07-10 03:42:35 +00:00
|
|
|
|
2017-06-29 17:43:35 +00:00
|
|
|
this.videoSwitchInProgress = true;
|
2017-10-12 23:02:29 +00:00
|
|
|
|
|
|
|
return this._createDesktopTrack(options)
|
2017-10-16 20:37:13 +00:00
|
|
|
.then(stream => this.useVideoStream(stream))
|
|
|
|
.then(() => {
|
|
|
|
this.videoSwitchInProgress = false;
|
2018-01-03 21:24:07 +00:00
|
|
|
sendAnalytics(createScreenSharingEvent('started'));
|
|
|
|
logger.log('Screen sharing started');
|
2017-10-16 20:37:13 +00:00
|
|
|
})
|
|
|
|
.catch(error => {
|
|
|
|
this.videoSwitchInProgress = false;
|
2016-03-04 21:20:05 +00:00
|
|
|
|
2017-10-16 20:37:13 +00:00
|
|
|
// Pawel: With this call I'm trying to preserve the original
|
|
|
|
// behaviour although it is not clear why would we "untoggle"
|
|
|
|
// on failure. I suppose it was to restore video in case there
|
|
|
|
// was some problem during "this.useVideoStream(desktopStream)".
|
|
|
|
// It's important to note that the handler will not be available
|
|
|
|
// if we fail early on trying to get desktop media (which makes
|
|
|
|
// sense, because the camera video is still being used, so
|
|
|
|
// nothing to "untoggle").
|
|
|
|
if (this._untoggleScreenSharing) {
|
|
|
|
this._untoggleScreenSharing();
|
|
|
|
}
|
2017-08-21 08:57:58 +00:00
|
|
|
|
2017-10-16 20:37:13 +00:00
|
|
|
// FIXME the code inside of _handleScreenSharingError is
|
|
|
|
// asynchronous, but does not return a Promise and is not part
|
|
|
|
// of the current Promise chain.
|
|
|
|
this._handleScreenSharingError(error);
|
|
|
|
|
|
|
|
return Promise.reject(error);
|
|
|
|
});
|
2017-06-29 17:43:35 +00:00
|
|
|
},
|
2016-03-04 21:55:44 +00:00
|
|
|
|
2017-06-29 17:43:35 +00:00
|
|
|
/**
|
|
|
|
* Handles {@link JitsiTrackError} returned by the lib-jitsi-meet when
|
|
|
|
* trying to create screensharing track. It will either do nothing if
|
|
|
|
* the dialog was canceled on user's request or display inline installation
|
|
|
|
* dialog and ask the user to install the extension, once the extension is
|
|
|
|
* installed it will switch the conference to screensharing. The last option
|
|
|
|
* is that an unrecoverable error dialog will be displayed.
|
|
|
|
* @param {JitsiTrackError} error - The error returned by
|
|
|
|
* {@link _createDesktopTrack} Promise.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_handleScreenSharingError(error) {
|
2017-10-10 23:31:40 +00:00
|
|
|
if (error.name === JitsiTrackErrors.CHROME_EXTENSION_USER_CANCELED) {
|
2017-06-29 17:43:35 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.error('failed to share local desktop', error);
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
if (error.name
|
|
|
|
=== JitsiTrackErrors.CHROME_EXTENSION_USER_GESTURE_REQUIRED) {
|
2017-06-29 17:43:35 +00:00
|
|
|
// If start with screen sharing the extension will fail to install
|
|
|
|
// (if not found), because the request has been triggered by the
|
|
|
|
// script. Show a dialog which asks user to click "install" and try
|
|
|
|
// again switching to the screen sharing.
|
|
|
|
APP.UI.showExtensionInlineInstallationDialog(
|
|
|
|
() => {
|
2017-10-12 23:02:29 +00:00
|
|
|
// eslint-disable-next-line no-empty-function
|
2017-08-21 08:57:58 +00:00
|
|
|
this.toggleScreenSharing().catch(() => {});
|
2016-05-27 15:49:26 +00:00
|
|
|
}
|
2017-06-29 17:43:35 +00:00
|
|
|
);
|
2016-05-26 08:53:02 +00:00
|
|
|
|
2017-06-29 17:43:35 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handling:
|
2017-10-10 23:31:40 +00:00
|
|
|
// JitsiTrackErrors.PERMISSION_DENIED
|
|
|
|
// JitsiTrackErrors.CHROME_EXTENSION_INSTALLATION_ERROR
|
2019-01-10 23:39:17 +00:00
|
|
|
// JitsiTrackErrors.CONSTRAINT_FAILED
|
2017-10-10 23:31:40 +00:00
|
|
|
// JitsiTrackErrors.GENERAL
|
2017-06-29 17:43:35 +00:00
|
|
|
// and any other
|
2017-11-03 19:05:03 +00:00
|
|
|
let descriptionKey;
|
|
|
|
let titleKey;
|
2017-06-29 17:43:35 +00:00
|
|
|
|
2017-10-10 23:31:40 +00:00
|
|
|
if (error.name === JitsiTrackErrors.PERMISSION_DENIED) {
|
2018-03-21 22:19:01 +00:00
|
|
|
|
|
|
|
// in FF the only option for user is to deny access temporary or
|
|
|
|
// permanently and we only receive permission_denied
|
|
|
|
// we always show some info cause in case of permanently, no info
|
|
|
|
// shown will be bad experience
|
|
|
|
//
|
|
|
|
// TODO: detect interval between requesting permissions and received
|
|
|
|
// error, this way we can detect user interaction which will have
|
|
|
|
// longer delay
|
|
|
|
if (JitsiMeetJS.util.browser.isFirefox()) {
|
|
|
|
descriptionKey
|
|
|
|
= 'dialog.screenSharingFirefoxPermissionDeniedError';
|
|
|
|
titleKey = 'dialog.screenSharingFirefoxPermissionDeniedTitle';
|
|
|
|
} else {
|
|
|
|
descriptionKey = 'dialog.screenSharingPermissionDeniedError';
|
|
|
|
titleKey = 'dialog.screenSharingFailedToInstallTitle';
|
|
|
|
}
|
2019-01-10 23:39:17 +00:00
|
|
|
} else if (error.name === JitsiTrackErrors.CONSTRAINT_FAILED) {
|
|
|
|
descriptionKey = 'dialog.cameraConstraintFailedError';
|
|
|
|
titleKey = 'deviceError.cameraError';
|
2016-02-16 13:33:53 +00:00
|
|
|
} else {
|
2017-11-03 19:05:03 +00:00
|
|
|
descriptionKey = 'dialog.screenSharingFailedToInstall';
|
|
|
|
titleKey = 'dialog.screenSharingFailedToInstallTitle';
|
2016-02-04 15:25:11 +00:00
|
|
|
}
|
2017-06-29 17:43:35 +00:00
|
|
|
|
2017-11-03 19:05:03 +00:00
|
|
|
APP.UI.messageHandler.showError({
|
|
|
|
descriptionKey,
|
|
|
|
titleKey
|
|
|
|
});
|
2016-02-04 15:25:11 +00:00
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-01-15 14:59:35 +00:00
|
|
|
/**
|
|
|
|
* Setup interaction between conference and UI.
|
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
_setupListeners() {
|
2016-01-06 22:39:13 +00:00
|
|
|
// add local streams when joined to the conference
|
2017-10-10 23:31:40 +00:00
|
|
|
room.on(JitsiConferenceEvents.CONFERENCE_JOINED, () => {
|
2017-11-16 22:54:49 +00:00
|
|
|
this._onConferenceJoined();
|
2016-01-06 22:39:13 +00:00
|
|
|
});
|
|
|
|
|
2017-03-07 16:50:17 +00:00
|
|
|
room.on(
|
2017-10-10 23:31:40 +00:00
|
|
|
JitsiConferenceEvents.CONFERENCE_LEFT,
|
2017-02-27 21:42:28 +00:00
|
|
|
(...args) => APP.store.dispatch(conferenceLeft(room, ...args)));
|
|
|
|
|
2016-02-25 12:32:52 +00:00
|
|
|
room.on(
|
2017-10-10 23:31:40 +00:00
|
|
|
JitsiConferenceEvents.AUTH_STATUS_CHANGED,
|
2017-05-26 16:28:14 +00:00
|
|
|
(authEnabled, authLogin) =>
|
2018-06-20 20:19:53 +00:00
|
|
|
APP.store.dispatch(authStatusChanged(authEnabled, authLogin)));
|
2016-02-25 12:32:52 +00:00
|
|
|
|
2017-10-10 23:31:40 +00:00
|
|
|
room.on(JitsiConferenceEvents.PARTCIPANT_FEATURES_CHANGED,
|
2017-01-06 01:18:07 +00:00
|
|
|
user => APP.UI.onUserFeaturesChanged(user));
|
2017-10-10 23:31:40 +00:00
|
|
|
room.on(JitsiConferenceEvents.USER_JOINED, (id, user) => {
|
2018-08-16 15:11:06 +00:00
|
|
|
// The logic shared between RN and web.
|
|
|
|
commonUserJoinedHandling(APP.store, room, user);
|
2018-07-26 16:33:40 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
if (user.isHidden()) {
|
2016-04-26 21:38:07 +00:00
|
|
|
return;
|
2017-10-12 23:02:29 +00:00
|
|
|
}
|
2016-04-26 21:38:07 +00:00
|
|
|
|
2018-08-16 15:11:06 +00:00
|
|
|
const displayName = user.getDisplayName();
|
2017-02-27 21:42:28 +00:00
|
|
|
|
2018-07-23 18:11:47 +00:00
|
|
|
logger.log(`USER ${id} connnected:`, user);
|
2017-12-05 03:27:17 +00:00
|
|
|
APP.API.notifyUserJoined(id, {
|
|
|
|
displayName,
|
|
|
|
formattedDisplayName: appendSuffix(
|
|
|
|
displayName || interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME)
|
|
|
|
});
|
2016-09-16 20:17:00 +00:00
|
|
|
APP.UI.addUser(user);
|
2016-01-13 21:17:33 +00:00
|
|
|
|
2016-04-26 21:38:07 +00:00
|
|
|
// check the roles for the new user and reflect them
|
2016-01-13 21:17:33 +00:00
|
|
|
APP.UI.updateUserRole(user);
|
2016-01-06 22:39:13 +00:00
|
|
|
});
|
2017-08-29 15:08:16 +00:00
|
|
|
|
2017-10-10 23:31:40 +00:00
|
|
|
room.on(JitsiConferenceEvents.USER_LEFT, (id, user) => {
|
2018-08-16 15:11:06 +00:00
|
|
|
// The logic shared between RN and web.
|
|
|
|
commonUserLeftHandling(APP.store, room, user);
|
2018-07-26 16:33:40 +00:00
|
|
|
|
2018-08-16 15:11:06 +00:00
|
|
|
if (user.isHidden()) {
|
2018-02-22 19:33:10 +00:00
|
|
|
return;
|
|
|
|
}
|
2018-07-26 16:33:40 +00:00
|
|
|
|
2018-07-23 18:11:47 +00:00
|
|
|
logger.log(`USER ${id} LEFT:`, user);
|
2016-01-14 15:05:54 +00:00
|
|
|
APP.API.notifyUserLeft(id);
|
2018-05-21 21:22:41 +00:00
|
|
|
APP.UI.messageHandler.participantNotification(
|
|
|
|
user.getDisplayName(),
|
|
|
|
'notify.somebody',
|
|
|
|
'disconnected',
|
|
|
|
'notify.disconnected');
|
2016-04-28 16:54:10 +00:00
|
|
|
APP.UI.onSharedVideoStop(id);
|
2016-01-06 22:39:13 +00:00
|
|
|
});
|
|
|
|
|
2017-10-10 23:31:40 +00:00
|
|
|
room.on(JitsiConferenceEvents.USER_STATUS_CHANGED, (id, status) => {
|
2017-07-31 23:33:22 +00:00
|
|
|
APP.store.dispatch(participantPresenceChanged(id, status));
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
const user = room.getParticipantById(id);
|
|
|
|
|
2017-05-19 15:12:24 +00:00
|
|
|
if (user) {
|
|
|
|
APP.UI.updateUserStatus(user, status);
|
|
|
|
}
|
|
|
|
});
|
2016-01-06 22:39:13 +00:00
|
|
|
|
2017-10-10 23:31:40 +00:00
|
|
|
room.on(JitsiConferenceEvents.USER_ROLE_CHANGED, (id, role) => {
|
2016-01-06 22:39:13 +00:00
|
|
|
if (this.isLocalId(id)) {
|
2016-11-11 15:00:54 +00:00
|
|
|
logger.info(`My role changed, new role: ${role}`);
|
2017-04-10 21:53:30 +00:00
|
|
|
|
|
|
|
APP.store.dispatch(localParticipantRoleChanged(role));
|
|
|
|
|
2016-09-28 18:41:02 +00:00
|
|
|
if (this.isModerator !== room.isModerator()) {
|
|
|
|
this.isModerator = room.isModerator();
|
|
|
|
APP.UI.updateLocalRole(room.isModerator());
|
|
|
|
}
|
2016-01-06 22:39:13 +00:00
|
|
|
} else {
|
2017-04-10 21:53:30 +00:00
|
|
|
APP.store.dispatch(participantRoleChanged(id, role));
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
const user = room.getParticipantById(id);
|
|
|
|
|
2016-01-06 22:39:13 +00:00
|
|
|
if (user) {
|
|
|
|
APP.UI.updateUserRole(user);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
room.on(JitsiConferenceEvents.TRACK_ADDED, track => {
|
|
|
|
if (!track || track.isLocal()) {
|
2016-01-06 22:39:13 +00:00
|
|
|
return;
|
2017-10-12 23:02:29 +00:00
|
|
|
}
|
2016-01-29 22:06:54 +00:00
|
|
|
|
2017-06-20 16:30:54 +00:00
|
|
|
APP.store.dispatch(trackAdded(track));
|
2016-01-06 22:39:13 +00:00
|
|
|
});
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
room.on(JitsiConferenceEvents.TRACK_REMOVED, track => {
|
|
|
|
if (!track || track.isLocal()) {
|
2016-02-23 22:47:55 +00:00
|
|
|
return;
|
2017-10-12 23:02:29 +00:00
|
|
|
}
|
2016-02-23 22:47:55 +00:00
|
|
|
|
2017-06-20 16:30:54 +00:00
|
|
|
APP.store.dispatch(trackRemoved(track));
|
2016-01-06 22:39:13 +00:00
|
|
|
});
|
|
|
|
|
2017-10-10 23:31:40 +00:00
|
|
|
room.on(JitsiConferenceEvents.TRACK_AUDIO_LEVEL_CHANGED, (id, lvl) => {
|
2017-10-12 23:02:29 +00:00
|
|
|
let newLvl = lvl;
|
|
|
|
|
2017-08-18 11:30:30 +00:00
|
|
|
if (this.isLocalId(id)
|
|
|
|
&& this.localAudio && this.localAudio.isMuted()) {
|
2017-10-12 23:02:29 +00:00
|
|
|
newLvl = 0;
|
2016-01-06 22:39:13 +00:00
|
|
|
}
|
2016-01-19 19:32:29 +00:00
|
|
|
|
2017-08-18 11:30:30 +00:00
|
|
|
if (config.debug) {
|
2017-10-12 23:02:29 +00:00
|
|
|
this.audioLevelsMap[id] = newLvl;
|
|
|
|
if (config.debugAudioLevels) {
|
|
|
|
logger.log(`AudioLevel:${id}/${newLvl}`);
|
|
|
|
}
|
2016-02-09 22:49:46 +00:00
|
|
|
}
|
2016-01-19 19:32:29 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
APP.UI.setAudioLevel(id, newLvl);
|
2016-01-06 22:39:13 +00:00
|
|
|
});
|
|
|
|
|
2017-10-10 23:31:40 +00:00
|
|
|
room.on(JitsiConferenceEvents.TALK_WHILE_MUTED, () => {
|
2016-09-09 19:37:47 +00:00
|
|
|
APP.UI.showToolbar(6000);
|
2016-09-09 01:19:18 +00:00
|
|
|
});
|
2019-02-17 14:40:24 +00:00
|
|
|
room.on(JitsiConferenceEvents.SUBJECT_CHANGED,
|
2019-03-12 17:45:53 +00:00
|
|
|
subject => APP.store.dispatch(conferenceSubjectChanged(subject)));
|
2016-09-09 01:19:18 +00:00
|
|
|
|
2016-01-26 21:26:28 +00:00
|
|
|
room.on(
|
2017-10-10 23:31:40 +00:00
|
|
|
JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED,
|
2017-10-02 23:08:07 +00:00
|
|
|
(leavingIds, enteringIds) =>
|
|
|
|
APP.UI.handleLastNEndpoints(leavingIds, enteringIds));
|
2017-03-21 17:14:13 +00:00
|
|
|
|
2017-08-09 19:40:03 +00:00
|
|
|
room.on(
|
2017-10-10 23:31:40 +00:00
|
|
|
JitsiConferenceEvents.P2P_STATUS,
|
2017-10-12 23:02:29 +00:00
|
|
|
(jitsiConference, p2p) =>
|
|
|
|
APP.store.dispatch(p2pStatusChanged(p2p)));
|
2017-08-09 19:40:03 +00:00
|
|
|
|
2016-09-16 20:51:19 +00:00
|
|
|
room.on(
|
2017-10-10 23:31:40 +00:00
|
|
|
JitsiConferenceEvents.PARTICIPANT_CONN_STATUS_CHANGED,
|
2018-05-21 21:06:02 +00:00
|
|
|
(id, connectionStatus) => APP.store.dispatch(
|
|
|
|
participantConnectionStatusChanged(id, connectionStatus)));
|
2017-07-13 00:26:10 +00:00
|
|
|
|
Associate remote participant w/ JitsiConference (_UPDATED)
The commit message of "Associate remote participant w/ JitsiConference
(_JOINED)" explains the motivation for this commit.
Practically, _JOINED and _LEFT combined with "Remove remote participants
who are no longer of interest" should alleviate the problem with
multiplying remote participants to an acceptable level of annoyance.
Technically though, a remote participant cannot be identified by an ID
only. The ID is (somewhat) "unique" in the context of a single
JitsiConference instance. So in order to not have to scratch our heads
over an obscure corner, racing case, it's better to always identify
remote participants by the pair id-conference. Unfortunately, that's a
bit of a high order given the existing source code. So I've implemented
the cases which are the easiest so that new source code written with
participantUpdated is more likely to identify a remote participant with
the pair id-conference.
Additionally, the commit "Reduce direct read access to the
features/base/participants redux state" brings more control back to the
functions of the feature base/participants so that one day we can (if we
choose to) do something like, for example:
If getParticipants is called with a conference, it returns the
participants from features/base/participants who are associated with the
specified conference. If no conference is specified in the function
call, then default to the conference which is the primary focus of the
app at the time of the function call. Added to the above, this should
allow us to further reduce the cases in which we're identifying remote
participants by id only and get us even closer to a more "predictable"
behavior in corner, racing cases.
2018-05-22 21:47:43 +00:00
|
|
|
room.on(
|
|
|
|
JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
|
|
|
|
id => APP.store.dispatch(dominantSpeakerChanged(id, room)));
|
2016-01-06 22:39:13 +00:00
|
|
|
|
2017-10-10 23:31:40 +00:00
|
|
|
room.on(JitsiConferenceEvents.CONNECTION_INTERRUPTED, () => {
|
2017-07-13 00:26:10 +00:00
|
|
|
APP.store.dispatch(localParticipantConnectionStatusChanged(
|
2017-10-10 23:31:40 +00:00
|
|
|
JitsiParticipantConnectionStatus.INTERRUPTED));
|
2016-07-22 18:42:41 +00:00
|
|
|
});
|
|
|
|
|
2017-10-10 23:31:40 +00:00
|
|
|
room.on(JitsiConferenceEvents.CONNECTION_RESTORED, () => {
|
2017-07-13 00:26:10 +00:00
|
|
|
APP.store.dispatch(localParticipantConnectionStatusChanged(
|
2017-10-10 23:31:40 +00:00
|
|
|
JitsiParticipantConnectionStatus.ACTIVE));
|
2016-07-22 18:42:41 +00:00
|
|
|
});
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
room.on(
|
|
|
|
JitsiConferenceEvents.DISPLAY_NAME_CHANGED,
|
|
|
|
(id, displayName) => {
|
|
|
|
const formattedDisplayName
|
2019-01-15 11:28:07 +00:00
|
|
|
= getNormalizedDisplayName(displayName);
|
2017-10-12 23:02:29 +00:00
|
|
|
|
|
|
|
APP.store.dispatch(participantUpdated({
|
Associate remote participant w/ JitsiConference (_UPDATED)
The commit message of "Associate remote participant w/ JitsiConference
(_JOINED)" explains the motivation for this commit.
Practically, _JOINED and _LEFT combined with "Remove remote participants
who are no longer of interest" should alleviate the problem with
multiplying remote participants to an acceptable level of annoyance.
Technically though, a remote participant cannot be identified by an ID
only. The ID is (somewhat) "unique" in the context of a single
JitsiConference instance. So in order to not have to scratch our heads
over an obscure corner, racing case, it's better to always identify
remote participants by the pair id-conference. Unfortunately, that's a
bit of a high order given the existing source code. So I've implemented
the cases which are the easiest so that new source code written with
participantUpdated is more likely to identify a remote participant with
the pair id-conference.
Additionally, the commit "Reduce direct read access to the
features/base/participants redux state" brings more control back to the
functions of the feature base/participants so that one day we can (if we
choose to) do something like, for example:
If getParticipants is called with a conference, it returns the
participants from features/base/participants who are associated with the
specified conference. If no conference is specified in the function
call, then default to the conference which is the primary focus of the
app at the time of the function call. Added to the above, this should
allow us to further reduce the cases in which we're identifying remote
participants by id only and get us even closer to a more "predictable"
behavior in corner, racing cases.
2018-05-22 21:47:43 +00:00
|
|
|
conference: room,
|
2017-10-12 23:02:29 +00:00
|
|
|
id,
|
|
|
|
name: formattedDisplayName
|
|
|
|
}));
|
2017-12-05 03:27:17 +00:00
|
|
|
APP.API.notifyDisplayNameChanged(id, {
|
|
|
|
displayName: formattedDisplayName,
|
|
|
|
formattedDisplayName:
|
|
|
|
appendSuffix(
|
|
|
|
formattedDisplayName
|
|
|
|
|| interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME)
|
|
|
|
});
|
2017-10-12 23:02:29 +00:00
|
|
|
APP.UI.changeDisplayName(id, formattedDisplayName);
|
|
|
|
}
|
|
|
|
);
|
2018-06-26 22:56:22 +00:00
|
|
|
room.on(
|
|
|
|
JitsiConferenceEvents.BOT_TYPE_CHANGED,
|
|
|
|
(id, botType) => {
|
|
|
|
|
|
|
|
APP.store.dispatch(participantUpdated({
|
|
|
|
conference: room,
|
|
|
|
id,
|
|
|
|
botType
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
);
|
2016-01-06 22:39:13 +00:00
|
|
|
|
2018-07-17 17:31:12 +00:00
|
|
|
room.on(
|
|
|
|
JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
|
|
|
|
(...args) => APP.store.dispatch(endpointMessageReceived(...args)));
|
|
|
|
|
2017-04-18 21:49:52 +00:00
|
|
|
room.on(
|
2017-10-10 23:31:40 +00:00
|
|
|
JitsiConferenceEvents.LOCK_STATE_CHANGED,
|
2017-04-18 21:49:52 +00:00
|
|
|
(...args) => APP.store.dispatch(lockStateChanged(room, ...args)));
|
2017-04-13 00:23:43 +00:00
|
|
|
|
2017-08-17 16:43:22 +00:00
|
|
|
APP.remoteControl.on(RemoteControlEvents.ACTIVE_CHANGED, isActive => {
|
|
|
|
room.setLocalParticipantProperty(
|
2017-10-12 23:02:29 +00:00
|
|
|
'remoteControlSessionStatus',
|
2017-08-17 16:43:22 +00:00
|
|
|
isActive
|
|
|
|
);
|
|
|
|
APP.UI.setLocalRemoteControlActiveChanged();
|
|
|
|
});
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
/* eslint-disable max-params */
|
2017-10-02 23:08:07 +00:00
|
|
|
room.on(
|
2017-10-10 23:31:40 +00:00
|
|
|
JitsiConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
|
2017-10-02 23:08:07 +00:00
|
|
|
(participant, name, oldValue, newValue) => {
|
|
|
|
switch (name) {
|
|
|
|
case 'remoteControlSessionStatus':
|
|
|
|
APP.UI.setRemoteControlActiveStatus(
|
|
|
|
participant.getId(),
|
|
|
|
newValue);
|
|
|
|
break;
|
|
|
|
default:
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2017-10-02 23:08:07 +00:00
|
|
|
// ignore
|
|
|
|
}
|
|
|
|
});
|
2016-06-20 21:13:17 +00:00
|
|
|
|
2017-10-10 23:31:40 +00:00
|
|
|
room.on(JitsiConferenceEvents.KICKED, () => {
|
2016-01-29 15:33:16 +00:00
|
|
|
APP.UI.hideStats();
|
2016-01-06 22:39:13 +00:00
|
|
|
APP.UI.notifyKicked();
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-01-06 22:39:13 +00:00
|
|
|
// FIXME close
|
|
|
|
});
|
|
|
|
|
2017-10-10 23:31:40 +00:00
|
|
|
room.on(JitsiConferenceEvents.SUSPEND_DETECTED, () => {
|
2017-01-31 20:58:48 +00:00
|
|
|
APP.store.dispatch(suspendDetected());
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-11-09 22:34:01 +00:00
|
|
|
// After wake up, we will be in a state where conference is left
|
|
|
|
// there will be dialog shown to user.
|
|
|
|
// We do not want video/audio as we show an overlay and after it
|
|
|
|
// user need to rejoin or close, while waking up we can detect
|
|
|
|
// camera wakeup as a problem with device.
|
|
|
|
// We also do not care about device change, which happens
|
|
|
|
// on resume after suspending PC.
|
2017-10-12 23:02:29 +00:00
|
|
|
if (this.deviceChangeListener) {
|
2016-11-09 22:34:01 +00:00
|
|
|
JitsiMeetJS.mediaDevices.removeEventListener(
|
2017-10-10 23:31:40 +00:00
|
|
|
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
|
2016-11-09 22:34:01 +00:00
|
|
|
this.deviceChangeListener);
|
2017-10-12 23:02:29 +00:00
|
|
|
}
|
2016-11-09 22:34:01 +00:00
|
|
|
|
|
|
|
// stop local video
|
2017-08-18 11:30:30 +00:00
|
|
|
if (this.localVideo) {
|
|
|
|
this.localVideo.dispose();
|
|
|
|
this.localVideo = null;
|
2017-01-19 18:46:11 +00:00
|
|
|
}
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-11-09 22:34:01 +00:00
|
|
|
// stop local audio
|
2017-08-18 11:30:30 +00:00
|
|
|
if (this.localAudio) {
|
|
|
|
this.localAudio.dispose();
|
|
|
|
this.localAudio = null;
|
2017-01-19 18:46:11 +00:00
|
|
|
}
|
2016-11-09 22:34:01 +00:00
|
|
|
});
|
|
|
|
|
2017-07-24 13:56:57 +00:00
|
|
|
APP.UI.addListener(UIEvents.AUDIO_MUTED, muted => {
|
2017-07-25 10:05:08 +00:00
|
|
|
this.muteAudio(muted);
|
2017-07-24 13:56:57 +00:00
|
|
|
});
|
2017-04-05 15:14:26 +00:00
|
|
|
APP.UI.addListener(UIEvents.VIDEO_MUTED, muted => {
|
2018-04-10 22:03:10 +00:00
|
|
|
this.muteVideo(muted);
|
2017-04-05 15:14:26 +00:00
|
|
|
});
|
2016-01-06 22:39:13 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
room.addCommandListener(this.commands.defaults.ETHERPAD,
|
|
|
|
({ value }) => {
|
|
|
|
APP.UI.initEtherpad(value);
|
|
|
|
}
|
|
|
|
);
|
2016-01-06 22:39:13 +00:00
|
|
|
|
2018-06-18 09:19:07 +00:00
|
|
|
APP.UI.addListener(UIEvents.EMAIL_CHANGED,
|
|
|
|
this.changeLocalEmail.bind(this));
|
2016-06-13 21:11:44 +00:00
|
|
|
room.addCommandListener(this.commands.defaults.EMAIL, (data, from) => {
|
2017-03-23 18:01:33 +00:00
|
|
|
APP.store.dispatch(participantUpdated({
|
Associate remote participant w/ JitsiConference (_UPDATED)
The commit message of "Associate remote participant w/ JitsiConference
(_JOINED)" explains the motivation for this commit.
Practically, _JOINED and _LEFT combined with "Remove remote participants
who are no longer of interest" should alleviate the problem with
multiplying remote participants to an acceptable level of annoyance.
Technically though, a remote participant cannot be identified by an ID
only. The ID is (somewhat) "unique" in the context of a single
JitsiConference instance. So in order to not have to scratch our heads
over an obscure corner, racing case, it's better to always identify
remote participants by the pair id-conference. Unfortunately, that's a
bit of a high order given the existing source code. So I've implemented
the cases which are the easiest so that new source code written with
participantUpdated is more likely to identify a remote participant with
the pair id-conference.
Additionally, the commit "Reduce direct read access to the
features/base/participants redux state" brings more control back to the
functions of the feature base/participants so that one day we can (if we
choose to) do something like, for example:
If getParticipants is called with a conference, it returns the
participants from features/base/participants who are associated with the
specified conference. If no conference is specified in the function
call, then default to the conference which is the primary focus of the
app at the time of the function call. Added to the above, this should
allow us to further reduce the cases in which we're identifying remote
participants by id only and get us even closer to a more "predictable"
behavior in corner, racing cases.
2018-05-22 21:47:43 +00:00
|
|
|
conference: room,
|
2017-03-23 18:01:33 +00:00
|
|
|
id: from,
|
|
|
|
email: data.value
|
|
|
|
}));
|
2016-01-06 22:39:13 +00:00
|
|
|
});
|
|
|
|
|
2016-10-27 20:49:29 +00:00
|
|
|
room.addCommandListener(
|
|
|
|
this.commands.defaults.AVATAR_URL,
|
|
|
|
(data, from) => {
|
2017-02-27 21:42:28 +00:00
|
|
|
APP.store.dispatch(
|
2017-03-23 18:01:33 +00:00
|
|
|
participantUpdated({
|
Associate remote participant w/ JitsiConference (_UPDATED)
The commit message of "Associate remote participant w/ JitsiConference
(_JOINED)" explains the motivation for this commit.
Practically, _JOINED and _LEFT combined with "Remove remote participants
who are no longer of interest" should alleviate the problem with
multiplying remote participants to an acceptable level of annoyance.
Technically though, a remote participant cannot be identified by an ID
only. The ID is (somewhat) "unique" in the context of a single
JitsiConference instance. So in order to not have to scratch our heads
over an obscure corner, racing case, it's better to always identify
remote participants by the pair id-conference. Unfortunately, that's a
bit of a high order given the existing source code. So I've implemented
the cases which are the easiest so that new source code written with
participantUpdated is more likely to identify a remote participant with
the pair id-conference.
Additionally, the commit "Reduce direct read access to the
features/base/participants redux state" brings more control back to the
functions of the feature base/participants so that one day we can (if we
choose to) do something like, for example:
If getParticipants is called with a conference, it returns the
participants from features/base/participants who are associated with the
specified conference. If no conference is specified in the function
call, then default to the conference which is the primary focus of the
app at the time of the function call. Added to the above, this should
allow us to further reduce the cases in which we're identifying remote
participants by id only and get us even closer to a more "predictable"
behavior in corner, racing cases.
2018-05-22 21:47:43 +00:00
|
|
|
conference: room,
|
2017-03-23 18:01:33 +00:00
|
|
|
id: from,
|
|
|
|
avatarURL: data.value
|
|
|
|
}));
|
2017-10-02 23:08:07 +00:00
|
|
|
});
|
2016-01-06 22:39:13 +00:00
|
|
|
|
2016-08-31 16:41:17 +00:00
|
|
|
room.addCommandListener(this.commands.defaults.AVATAR_ID,
|
|
|
|
(data, from) => {
|
2017-02-27 21:42:28 +00:00
|
|
|
APP.store.dispatch(
|
2017-03-23 18:01:33 +00:00
|
|
|
participantUpdated({
|
Associate remote participant w/ JitsiConference (_UPDATED)
The commit message of "Associate remote participant w/ JitsiConference
(_JOINED)" explains the motivation for this commit.
Practically, _JOINED and _LEFT combined with "Remove remote participants
who are no longer of interest" should alleviate the problem with
multiplying remote participants to an acceptable level of annoyance.
Technically though, a remote participant cannot be identified by an ID
only. The ID is (somewhat) "unique" in the context of a single
JitsiConference instance. So in order to not have to scratch our heads
over an obscure corner, racing case, it's better to always identify
remote participants by the pair id-conference. Unfortunately, that's a
bit of a high order given the existing source code. So I've implemented
the cases which are the easiest so that new source code written with
participantUpdated is more likely to identify a remote participant with
the pair id-conference.
Additionally, the commit "Reduce direct read access to the
features/base/participants redux state" brings more control back to the
functions of the feature base/participants so that one day we can (if we
choose to) do something like, for example:
If getParticipants is called with a conference, it returns the
participants from features/base/participants who are associated with the
specified conference. If no conference is specified in the function
call, then default to the conference which is the primary focus of the
app at the time of the function call. Added to the above, this should
allow us to further reduce the cases in which we're identifying remote
participants by id only and get us even closer to a more "predictable"
behavior in corner, racing cases.
2018-05-22 21:47:43 +00:00
|
|
|
conference: room,
|
2017-03-23 18:01:33 +00:00
|
|
|
id: from,
|
|
|
|
avatarID: data.value
|
|
|
|
}));
|
2016-08-31 16:41:17 +00:00
|
|
|
});
|
|
|
|
|
2017-03-23 17:45:51 +00:00
|
|
|
APP.UI.addListener(UIEvents.NICKNAME_CHANGED,
|
|
|
|
this.changeLocalDisplayName.bind(this));
|
2016-06-13 21:11:44 +00:00
|
|
|
|
2015-12-31 15:23:23 +00:00
|
|
|
room.on(
|
2017-10-10 23:31:40 +00:00
|
|
|
JitsiConferenceEvents.START_MUTED_POLICY_CHANGED,
|
2016-03-02 15:39:39 +00:00
|
|
|
({ audio, video }) => {
|
2017-11-21 02:21:35 +00:00
|
|
|
APP.store.dispatch(
|
|
|
|
onStartMutedPolicyChanged(audio, video));
|
2016-01-06 22:39:13 +00:00
|
|
|
}
|
|
|
|
);
|
2017-10-10 23:31:40 +00:00
|
|
|
room.on(JitsiConferenceEvents.STARTED_MUTED, () => {
|
2016-01-13 03:38:58 +00:00
|
|
|
(room.isStartAudioMuted() || room.isStartVideoMuted())
|
|
|
|
&& APP.UI.notifyInitiallyMuted();
|
|
|
|
});
|
2016-01-06 22:39:13 +00:00
|
|
|
|
2017-08-09 19:40:03 +00:00
|
|
|
room.on(
|
2017-10-10 23:31:40 +00:00
|
|
|
JitsiConferenceEvents.DATA_CHANNEL_OPENED, () => {
|
2017-08-09 19:40:03 +00:00
|
|
|
APP.store.dispatch(dataChannelOpened());
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2016-01-06 22:39:13 +00:00
|
|
|
// call hangup
|
|
|
|
APP.UI.addListener(UIEvents.HANGUP, () => {
|
2016-10-05 21:33:09 +00:00
|
|
|
this.hangup(true);
|
2016-01-06 22:39:13 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// logout
|
|
|
|
APP.UI.addListener(UIEvents.LOGOUT, () => {
|
2016-10-05 21:33:09 +00:00
|
|
|
AuthHandler.logout(room).then(url => {
|
2016-02-25 13:52:15 +00:00
|
|
|
if (url) {
|
2017-02-05 03:51:41 +00:00
|
|
|
UIUtil.redirect(url);
|
2016-02-25 13:52:15 +00:00
|
|
|
} else {
|
2016-10-05 21:33:09 +00:00
|
|
|
this.hangup(true);
|
2016-02-25 13:52:15 +00:00
|
|
|
}
|
|
|
|
});
|
2016-01-06 22:39:13 +00:00
|
|
|
});
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
/* eslint-disable max-params */
|
2017-10-02 23:08:07 +00:00
|
|
|
APP.UI.addListener(
|
|
|
|
UIEvents.RESOLUTION_CHANGED,
|
2016-07-08 20:14:49 +00:00
|
|
|
(id, oldResolution, newResolution, delay) => {
|
2018-01-03 21:24:07 +00:00
|
|
|
sendAnalytics(createStreamSwitchDelayEvent(
|
|
|
|
{
|
|
|
|
'old_resolution': oldResolution,
|
|
|
|
'new_resolution': newResolution,
|
|
|
|
value: delay
|
|
|
|
}));
|
2017-10-02 23:08:07 +00:00
|
|
|
});
|
2016-01-06 22:39:13 +00:00
|
|
|
|
|
|
|
APP.UI.addListener(UIEvents.AUTH_CLICKED, () => {
|
|
|
|
AuthHandler.authenticate(room);
|
|
|
|
});
|
|
|
|
|
2016-02-09 10:19:43 +00:00
|
|
|
APP.UI.addListener(
|
|
|
|
UIEvents.VIDEO_DEVICE_CHANGED,
|
2017-10-12 23:02:29 +00:00
|
|
|
cameraDeviceId => {
|
2017-10-17 18:20:33 +00:00
|
|
|
const videoWasMuted = this.isLocalVideoMuted();
|
|
|
|
|
2018-01-03 21:24:07 +00:00
|
|
|
sendAnalytics(createDeviceChangedEvent('video', 'input'));
|
2017-08-14 13:25:37 +00:00
|
|
|
createLocalTracksF({
|
2017-10-12 23:02:29 +00:00
|
|
|
devices: [ 'video' ],
|
|
|
|
cameraDeviceId,
|
2016-06-24 09:47:13 +00:00
|
|
|
micDeviceId: null
|
|
|
|
})
|
2017-10-12 23:02:29 +00:00
|
|
|
.then(([ stream ]) => {
|
2017-10-17 18:20:33 +00:00
|
|
|
// if we are in audio only mode or video was muted before
|
|
|
|
// changing device, then mute
|
|
|
|
if (this.isAudioOnly() || videoWasMuted) {
|
2017-04-05 15:14:26 +00:00
|
|
|
return stream.mute()
|
|
|
|
.then(() => stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
return stream;
|
|
|
|
})
|
|
|
|
.then(stream => {
|
2017-10-17 18:47:39 +00:00
|
|
|
// if we are screen sharing we do not want to stop it
|
|
|
|
if (this.isSharingScreen) {
|
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.useVideoStream(stream);
|
|
|
|
})
|
|
|
|
.then(() => {
|
2016-11-11 15:00:54 +00:00
|
|
|
logger.log('switched local video device');
|
2018-04-12 19:58:20 +00:00
|
|
|
APP.store.dispatch(updateSettings({
|
|
|
|
cameraDeviceId
|
|
|
|
}));
|
2016-06-24 09:47:13 +00:00
|
|
|
})
|
2017-10-12 23:02:29 +00:00
|
|
|
.catch(err => {
|
2017-07-31 18:36:41 +00:00
|
|
|
APP.UI.showCameraErrorNotification(err);
|
2016-06-24 09:47:13 +00:00
|
|
|
});
|
2016-02-09 10:19:43 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
APP.UI.addListener(
|
|
|
|
UIEvents.AUDIO_DEVICE_CHANGED,
|
2017-10-12 23:02:29 +00:00
|
|
|
micDeviceId => {
|
2017-10-17 18:20:33 +00:00
|
|
|
const audioWasMuted = this.isLocalAudioMuted();
|
|
|
|
|
2018-01-03 21:24:07 +00:00
|
|
|
sendAnalytics(createDeviceChangedEvent('audio', 'input'));
|
2017-08-14 13:25:37 +00:00
|
|
|
createLocalTracksF({
|
2017-10-12 23:02:29 +00:00
|
|
|
devices: [ 'audio' ],
|
2016-06-24 09:47:13 +00:00
|
|
|
cameraDeviceId: null,
|
2017-10-12 23:02:29 +00:00
|
|
|
micDeviceId
|
2016-06-24 09:47:13 +00:00
|
|
|
})
|
2017-10-12 23:02:29 +00:00
|
|
|
.then(([ stream ]) => {
|
2017-10-17 18:20:33 +00:00
|
|
|
// if audio was muted before changing the device, mute
|
|
|
|
// with the new device
|
|
|
|
if (audioWasMuted) {
|
|
|
|
return stream.mute()
|
|
|
|
.then(() => stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
return stream;
|
|
|
|
})
|
|
|
|
.then(stream => {
|
2016-06-24 09:47:13 +00:00
|
|
|
this.useAudioStream(stream);
|
2016-11-11 15:00:54 +00:00
|
|
|
logger.log('switched local audio device');
|
2018-04-12 19:58:20 +00:00
|
|
|
APP.store.dispatch(updateSettings({
|
|
|
|
micDeviceId
|
|
|
|
}));
|
2016-06-24 09:47:13 +00:00
|
|
|
})
|
2017-10-12 23:02:29 +00:00
|
|
|
.catch(err => {
|
2017-07-31 18:36:41 +00:00
|
|
|
APP.UI.showMicErrorNotification(err);
|
2016-06-24 09:47:13 +00:00
|
|
|
});
|
2016-02-09 10:19:43 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2017-04-05 15:14:26 +00:00
|
|
|
APP.UI.addListener(UIEvents.TOGGLE_AUDIO_ONLY, audioOnly => {
|
2017-07-24 15:36:19 +00:00
|
|
|
|
|
|
|
// FIXME On web video track is stored both in redux and in
|
|
|
|
// 'localVideo' field, video is attempted to be unmuted twice when
|
|
|
|
// turning off the audio only mode. This will crash the app with
|
|
|
|
// 'unmute operation is already in progress'.
|
|
|
|
// Because there's no logic in redux about creating new track in
|
|
|
|
// case unmute when not track exists the things have to go through
|
|
|
|
// muteVideo logic in such case.
|
|
|
|
const tracks = APP.store.getState()['features/base/tracks'];
|
|
|
|
const isTrackInRedux
|
|
|
|
= Boolean(
|
|
|
|
tracks.find(
|
|
|
|
track => track.jitsiTrack
|
|
|
|
&& track.jitsiTrack.getType() === 'video'));
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2017-07-24 15:36:19 +00:00
|
|
|
if (!isTrackInRedux) {
|
|
|
|
this.muteVideo(audioOnly);
|
|
|
|
}
|
2017-04-05 15:14:26 +00:00
|
|
|
|
|
|
|
// Immediately update the UI by having remote videos and the large
|
|
|
|
// video update themselves instead of waiting for some other event
|
|
|
|
// to cause the update, usually PARTICIPANT_CONN_STATUS_CHANGED.
|
|
|
|
// There is no guarantee another event will trigger the update
|
|
|
|
// immediately and in all situations, for example because a remote
|
|
|
|
// participant is having connection trouble so no status changes.
|
|
|
|
APP.UI.updateAllVideos();
|
|
|
|
});
|
|
|
|
|
2016-02-04 15:25:11 +00:00
|
|
|
APP.UI.addListener(
|
|
|
|
UIEvents.TOGGLE_SCREENSHARING, this.toggleScreenSharing.bind(this)
|
2016-01-06 22:39:13 +00:00
|
|
|
);
|
2016-03-18 20:00:55 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
/* eslint-disable max-params */
|
2017-10-02 23:08:07 +00:00
|
|
|
APP.UI.addListener(
|
|
|
|
UIEvents.UPDATE_SHARED_VIDEO,
|
2016-04-25 20:39:31 +00:00
|
|
|
(url, state, time, isMuted, volume) => {
|
2017-10-12 23:02:29 +00:00
|
|
|
/* eslint-enable max-params */
|
2017-10-02 23:08:07 +00:00
|
|
|
// send start and stop commands once, and remove any updates
|
|
|
|
// that had left
|
|
|
|
if (state === 'stop'
|
|
|
|
|| state === 'start'
|
|
|
|
|| state === 'playing') {
|
|
|
|
room.removeCommand(this.commands.defaults.SHARED_VIDEO);
|
|
|
|
room.sendCommandOnce(this.commands.defaults.SHARED_VIDEO, {
|
|
|
|
value: url,
|
|
|
|
attributes: {
|
2017-10-12 23:02:29 +00:00
|
|
|
state,
|
|
|
|
time,
|
2017-10-02 23:08:07 +00:00
|
|
|
muted: isMuted,
|
2017-10-12 23:02:29 +00:00
|
|
|
volume
|
2017-10-02 23:08:07 +00:00
|
|
|
}
|
|
|
|
});
|
2017-10-12 23:02:29 +00:00
|
|
|
} else {
|
2017-10-02 23:08:07 +00:00
|
|
|
// in case of paused, in order to allow late users to join
|
|
|
|
// paused
|
|
|
|
room.removeCommand(this.commands.defaults.SHARED_VIDEO);
|
|
|
|
room.sendCommand(this.commands.defaults.SHARED_VIDEO, {
|
|
|
|
value: url,
|
|
|
|
attributes: {
|
2017-10-12 23:02:29 +00:00
|
|
|
state,
|
|
|
|
time,
|
2017-10-02 23:08:07 +00:00
|
|
|
muted: isMuted,
|
2017-10-12 23:02:29 +00:00
|
|
|
volume
|
2017-10-02 23:08:07 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2018-03-07 00:28:19 +00:00
|
|
|
|
|
|
|
APP.store.dispatch(setSharedVideoStatus(state));
|
2017-10-02 23:08:07 +00:00
|
|
|
});
|
2016-03-18 20:00:55 +00:00
|
|
|
room.addCommandListener(
|
2017-10-12 23:02:29 +00:00
|
|
|
this.commands.defaults.SHARED_VIDEO,
|
|
|
|
({ value, attributes }, id) => {
|
2016-03-18 20:00:55 +00:00
|
|
|
if (attributes.state === 'stop') {
|
2016-04-28 16:54:10 +00:00
|
|
|
APP.UI.onSharedVideoStop(id, attributes);
|
2017-10-12 23:02:29 +00:00
|
|
|
} else if (attributes.state === 'start') {
|
2016-04-28 16:54:10 +00:00
|
|
|
APP.UI.onSharedVideoStart(id, value, attributes);
|
2017-10-12 23:02:29 +00:00
|
|
|
} else if (attributes.state === 'playing'
|
2016-03-18 20:00:55 +00:00
|
|
|
|| attributes.state === 'pause') {
|
2016-04-28 16:54:10 +00:00
|
|
|
APP.UI.onSharedVideoUpdate(id, value, attributes);
|
2016-03-18 20:00:55 +00:00
|
|
|
}
|
|
|
|
});
|
2016-04-25 20:39:31 +00:00
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2017-11-16 22:54:49 +00:00
|
|
|
/**
|
|
|
|
* Callback invoked when the conference has been successfully joined.
|
|
|
|
* Initializes the UI and various other features.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
_onConferenceJoined() {
|
|
|
|
APP.UI.initConference();
|
|
|
|
|
|
|
|
APP.keyboardshortcut.init();
|
|
|
|
|
|
|
|
if (config.requireDisplayName
|
|
|
|
&& !APP.conference.getLocalDisplayName()) {
|
|
|
|
APP.UI.promptDisplayName();
|
|
|
|
}
|
|
|
|
|
|
|
|
APP.store.dispatch(conferenceJoined(room));
|
|
|
|
|
2018-04-12 19:58:20 +00:00
|
|
|
const displayName
|
|
|
|
= APP.store.getState()['features/base/settings'].displayName;
|
2017-12-05 03:27:17 +00:00
|
|
|
|
2018-06-22 04:33:33 +00:00
|
|
|
APP.UI.changeDisplayName('localVideoContainer', displayName);
|
2017-12-05 03:27:17 +00:00
|
|
|
APP.API.notifyConferenceJoined(
|
|
|
|
this.roomName,
|
|
|
|
this._room.myUserId(),
|
|
|
|
{
|
|
|
|
displayName,
|
|
|
|
formattedDisplayName: appendSuffix(
|
|
|
|
displayName,
|
|
|
|
interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME),
|
2017-12-19 23:11:54 +00:00
|
|
|
avatarURL: getAvatarURLByParticipantId(
|
|
|
|
APP.store.getState(), this._room.myUserId())
|
2017-12-05 03:27:17 +00:00
|
|
|
}
|
|
|
|
);
|
2017-11-16 22:54:49 +00:00
|
|
|
},
|
|
|
|
|
2016-04-25 20:39:31 +00:00
|
|
|
/**
|
2016-06-13 11:49:00 +00:00
|
|
|
* Adds any room listener.
|
2017-10-10 23:31:40 +00:00
|
|
|
* @param {string} eventName one of the JitsiConferenceEvents
|
2016-12-09 23:15:04 +00:00
|
|
|
* @param {Function} listener the function to be called when the event
|
|
|
|
* occurs
|
|
|
|
*/
|
|
|
|
addConferenceListener(eventName, listener) {
|
|
|
|
room.on(eventName, listener);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes any room listener.
|
2017-10-10 23:31:40 +00:00
|
|
|
* @param {string} eventName one of the JitsiConferenceEvents
|
2016-12-09 23:15:04 +00:00
|
|
|
* @param {Function} listener the listener to be removed.
|
2016-06-13 11:49:00 +00:00
|
|
|
*/
|
2016-12-09 23:15:04 +00:00
|
|
|
removeConferenceListener(eventName, listener) {
|
|
|
|
room.off(eventName, listener);
|
2016-06-13 11:49:00 +00:00
|
|
|
},
|
2016-12-09 23:15:04 +00:00
|
|
|
|
2016-06-13 11:49:00 +00:00
|
|
|
/**
|
|
|
|
* Inits list of current devices and event listener for device change.
|
|
|
|
* @private
|
2018-08-06 15:24:59 +00:00
|
|
|
* @returns {Promise}
|
2016-06-13 11:49:00 +00:00
|
|
|
*/
|
|
|
|
_initDeviceList() {
|
2018-07-12 03:57:44 +00:00
|
|
|
const { mediaDevices } = JitsiMeetJS;
|
|
|
|
|
|
|
|
if (mediaDevices.isDeviceListAvailable()
|
|
|
|
&& mediaDevices.isDeviceChangeAvailable()) {
|
core: refactor routing
Unfortunately, as the Jitsi Meet development evolved the routing mechanism
became more complex and thre logic ended up spread across multiple parts of the
codebase, which made it hard to follow and extend.
This change aims to fix that by rewriting the routing logic and centralizing it
in (pretty much) a single place, with no implicit inter-dependencies.
In order to arrive there, however, some extra changes were needed, which were
not caught early enough and are thus part of this change:
- JitsiMeetJS initialization is now synchronous: there is nothing async about
it, and the only async requirement (Temasys support) was lifted. See [0].
- WebRTC support can be detected early: building on top of the above, WebRTC
support can now be detected immediately, so take advantage of this to simplify
how we handle unsupported browsers. See [0].
The new router takes decissions based on the Redux state at the time of
invocation. A route can be represented by either a component or a URl reference,
with the latter taking precedence. On mobile, obviously, there is no concept of
URL reference so routing is based solely on components.
[0]: https://github.com/jitsi/lib-jitsi-meet/pull/779
2018-06-29 07:58:31 +00:00
|
|
|
this.deviceChangeListener = devices =>
|
2018-07-12 03:57:44 +00:00
|
|
|
window.setTimeout(() => this._onDeviceListChanged(devices), 0);
|
|
|
|
mediaDevices.addEventListener(
|
core: refactor routing
Unfortunately, as the Jitsi Meet development evolved the routing mechanism
became more complex and thre logic ended up spread across multiple parts of the
codebase, which made it hard to follow and extend.
This change aims to fix that by rewriting the routing logic and centralizing it
in (pretty much) a single place, with no implicit inter-dependencies.
In order to arrive there, however, some extra changes were needed, which were
not caught early enough and are thus part of this change:
- JitsiMeetJS initialization is now synchronous: there is nothing async about
it, and the only async requirement (Temasys support) was lifted. See [0].
- WebRTC support can be detected early: building on top of the above, WebRTC
support can now be detected immediately, so take advantage of this to simplify
how we handle unsupported browsers. See [0].
The new router takes decissions based on the Redux state at the time of
invocation. A route can be represented by either a component or a URl reference,
with the latter taking precedence. On mobile, obviously, there is no concept of
URL reference so routing is based solely on components.
[0]: https://github.com/jitsi/lib-jitsi-meet/pull/779
2018-06-29 07:58:31 +00:00
|
|
|
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
|
|
|
|
this.deviceChangeListener);
|
2018-08-06 15:24:59 +00:00
|
|
|
|
|
|
|
const { dispatch } = APP.store;
|
|
|
|
|
|
|
|
return dispatch(getAvailableDevices())
|
|
|
|
.then(devices => {
|
|
|
|
// Ugly way to synchronize real device IDs with local
|
|
|
|
// storage and settings menu. This is a workaround until
|
|
|
|
// getConstraints() method will be implemented in browsers.
|
|
|
|
if (this.localAudio) {
|
|
|
|
dispatch(updateSettings({
|
|
|
|
micDeviceId: this.localAudio.getDeviceId()
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2019-02-21 03:07:49 +00:00
|
|
|
if (this.localVideo
|
|
|
|
&& this.localVideo.videoType === 'camera') {
|
2018-08-06 15:24:59 +00:00
|
|
|
dispatch(updateSettings({
|
|
|
|
cameraDeviceId: this.localVideo.getDeviceId()
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
APP.UI.onAvailableDevicesChanged(devices);
|
|
|
|
});
|
core: refactor routing
Unfortunately, as the Jitsi Meet development evolved the routing mechanism
became more complex and thre logic ended up spread across multiple parts of the
codebase, which made it hard to follow and extend.
This change aims to fix that by rewriting the routing logic and centralizing it
in (pretty much) a single place, with no implicit inter-dependencies.
In order to arrive there, however, some extra changes were needed, which were
not caught early enough and are thus part of this change:
- JitsiMeetJS initialization is now synchronous: there is nothing async about
it, and the only async requirement (Temasys support) was lifted. See [0].
- WebRTC support can be detected early: building on top of the above, WebRTC
support can now be detected immediately, so take advantage of this to simplify
how we handle unsupported browsers. See [0].
The new router takes decissions based on the Redux state at the time of
invocation. A route can be represented by either a component or a URl reference,
with the latter taking precedence. On mobile, obviously, there is no concept of
URL reference so routing is based solely on components.
[0]: https://github.com/jitsi/lib-jitsi-meet/pull/779
2018-06-29 07:58:31 +00:00
|
|
|
}
|
2018-08-06 15:24:59 +00:00
|
|
|
|
|
|
|
return Promise.resolve();
|
2016-06-13 11:49:00 +00:00
|
|
|
},
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-06-13 11:49:00 +00:00
|
|
|
/**
|
|
|
|
* Event listener for JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED to
|
|
|
|
* handle change of available media devices.
|
|
|
|
* @private
|
|
|
|
* @param {MediaDeviceInfo[]} devices
|
|
|
|
* @returns {Promise}
|
|
|
|
*/
|
|
|
|
_onDeviceListChanged(devices) {
|
2018-07-13 17:31:28 +00:00
|
|
|
APP.store.dispatch(updateDeviceList(devices));
|
2016-06-13 11:49:00 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
const newDevices
|
|
|
|
= mediaDeviceHelper.getNewMediaDevicesAfterDeviceListChanged(
|
2017-08-18 11:30:30 +00:00
|
|
|
devices,
|
|
|
|
this.isSharingScreen,
|
|
|
|
this.localVideo,
|
|
|
|
this.localAudio);
|
2017-10-12 23:02:29 +00:00
|
|
|
const promises = [];
|
|
|
|
const audioWasMuted = this.isLocalAudioMuted();
|
|
|
|
const videoWasMuted = this.isLocalVideoMuted();
|
2019-04-12 16:10:38 +00:00
|
|
|
const requestedInput = {
|
|
|
|
audio: Boolean(newDevices.audioinput),
|
|
|
|
video: Boolean(newDevices.videoinput)
|
|
|
|
};
|
2016-06-13 11:49:00 +00:00
|
|
|
|
|
|
|
if (typeof newDevices.audiooutput !== 'undefined') {
|
2018-07-13 16:21:26 +00:00
|
|
|
const { dispatch } = APP.store;
|
|
|
|
const setAudioOutputPromise
|
|
|
|
= setAudioOutputDeviceId(newDevices.audiooutput, dispatch)
|
|
|
|
.catch(); // Just ignore any errors in catch block.
|
|
|
|
|
|
|
|
|
|
|
|
promises.push(setAudioOutputPromise);
|
2016-06-13 11:49:00 +00:00
|
|
|
}
|
|
|
|
|
2019-04-12 16:10:38 +00:00
|
|
|
// Handles the use case when the default device is changed (we are always stopping the streams because it's
|
|
|
|
// simpler):
|
|
|
|
// If the default device is changed we need to first stop the local streams and then call GUM. Otherwise GUM
|
|
|
|
// will return a stream using the old default device.
|
|
|
|
if (requestedInput.audio && this.localAudio) {
|
|
|
|
this.localAudio.stopStream();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (requestedInput.video && this.localVideo) {
|
|
|
|
this.localVideo.stopStream();
|
|
|
|
}
|
|
|
|
|
2016-06-13 11:49:00 +00:00
|
|
|
promises.push(
|
|
|
|
mediaDeviceHelper.createLocalTracksAfterDeviceListChanged(
|
2017-08-14 13:25:37 +00:00
|
|
|
createLocalTracksF,
|
2016-06-13 11:49:00 +00:00
|
|
|
newDevices.videoinput,
|
|
|
|
newDevices.audioinput)
|
2018-09-04 15:29:50 +00:00
|
|
|
.then(tracks => {
|
|
|
|
// If audio or video muted before, or we unplugged current
|
|
|
|
// device and selected new one, then mute new track.
|
|
|
|
const muteSyncPromises = tracks.map(track => {
|
|
|
|
if ((track.isVideoTrack() && videoWasMuted)
|
|
|
|
|| (track.isAudioTrack() && audioWasMuted)) {
|
|
|
|
return track.mute();
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
});
|
|
|
|
|
|
|
|
return Promise.all(muteSyncPromises)
|
2019-04-12 16:10:38 +00:00
|
|
|
.then(() =>
|
|
|
|
Promise.all(Object.keys(requestedInput).map(mediaType => {
|
|
|
|
if (requestedInput[mediaType]) {
|
|
|
|
const useStream
|
|
|
|
= mediaType === 'audio'
|
|
|
|
? this.useAudioStream.bind(this)
|
|
|
|
: this.useVideoStream.bind(this);
|
|
|
|
|
|
|
|
// Use the new stream or null if we failed to obtain it.
|
|
|
|
return useStream(tracks.find(track => track.getType() === mediaType) || null);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
})));
|
2018-09-04 15:29:50 +00:00
|
|
|
})
|
2016-06-13 11:49:00 +00:00
|
|
|
.then(() => {
|
2018-09-04 15:29:50 +00:00
|
|
|
// Log and sync known mute state.
|
2017-10-17 18:02:41 +00:00
|
|
|
if (audioWasMuted) {
|
2018-01-03 21:24:07 +00:00
|
|
|
sendAnalytics(createTrackMutedEvent(
|
|
|
|
'audio',
|
|
|
|
'device list changed'));
|
2017-10-09 21:40:38 +00:00
|
|
|
logger.log('Audio mute: device list changed');
|
2016-06-13 11:49:00 +00:00
|
|
|
muteLocalAudio(true);
|
|
|
|
}
|
|
|
|
|
2017-10-17 18:02:41 +00:00
|
|
|
if (!this.isSharingScreen && videoWasMuted) {
|
2018-01-03 21:24:07 +00:00
|
|
|
sendAnalytics(createTrackMutedEvent(
|
|
|
|
'video',
|
|
|
|
'device list changed'));
|
2017-10-09 21:40:38 +00:00
|
|
|
logger.log('Video mute: device list changed');
|
2016-06-13 11:49:00 +00:00
|
|
|
muteLocalVideo(true);
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
|
|
|
|
return Promise.all(promises)
|
|
|
|
.then(() => {
|
|
|
|
APP.UI.onAvailableDevicesChanged(devices);
|
|
|
|
});
|
2016-06-20 21:13:17 +00:00
|
|
|
},
|
2017-07-24 13:56:57 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines whether or not the audio button should be enabled.
|
|
|
|
*/
|
|
|
|
updateAudioIconEnabled() {
|
|
|
|
const audioMediaDevices
|
2019-03-28 16:29:30 +00:00
|
|
|
= APP.store.getState()['features/base/devices'].availableDevices.audioInput;
|
2017-07-24 13:56:57 +00:00
|
|
|
const audioDeviceCount
|
|
|
|
= audioMediaDevices ? audioMediaDevices.length : 0;
|
|
|
|
|
|
|
|
// The audio functionality is considered available if there are any
|
|
|
|
// audio devices detected or if the local audio stream already exists.
|
2017-08-18 11:30:30 +00:00
|
|
|
const available = audioDeviceCount > 0 || Boolean(this.localAudio);
|
2017-07-24 13:56:57 +00:00
|
|
|
|
|
|
|
logger.debug(
|
2017-10-12 23:02:29 +00:00
|
|
|
`Microphone button enabled: ${available}`,
|
|
|
|
`local audio: ${this.localAudio}`,
|
|
|
|
`audio devices: ${audioMediaDevices}`,
|
|
|
|
`device count: ${audioDeviceCount}`);
|
2017-07-24 13:56:57 +00:00
|
|
|
|
|
|
|
APP.store.dispatch(setAudioAvailable(available));
|
2017-08-04 08:15:11 +00:00
|
|
|
APP.API.notifyAudioAvailabilityChanged(available);
|
2017-07-24 13:56:57 +00:00
|
|
|
},
|
|
|
|
|
2017-06-28 14:15:46 +00:00
|
|
|
/**
|
|
|
|
* Determines whether or not the video button should be enabled.
|
|
|
|
*/
|
|
|
|
updateVideoIconEnabled() {
|
|
|
|
const videoMediaDevices
|
2019-03-28 16:29:30 +00:00
|
|
|
= APP.store.getState()['features/base/devices'].availableDevices.videoInput;
|
2017-06-28 14:15:46 +00:00
|
|
|
const videoDeviceCount
|
|
|
|
= videoMediaDevices ? videoMediaDevices.length : 0;
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2017-06-28 14:15:46 +00:00
|
|
|
// The video functionality is considered available if there are any
|
|
|
|
// video devices detected or if there is local video stream already
|
|
|
|
// active which could be either screensharing stream or a video track
|
|
|
|
// created before the permissions were rejected (through browser
|
|
|
|
// config).
|
2017-08-18 11:30:30 +00:00
|
|
|
const available = videoDeviceCount > 0 || Boolean(this.localVideo);
|
2017-06-28 14:15:46 +00:00
|
|
|
|
|
|
|
logger.debug(
|
2017-10-12 23:02:29 +00:00
|
|
|
`Camera button enabled: ${available}`,
|
|
|
|
`local video: ${this.localVideo}`,
|
|
|
|
`video devices: ${videoMediaDevices}`,
|
|
|
|
`device count: ${videoDeviceCount}`);
|
2017-06-28 14:15:46 +00:00
|
|
|
|
|
|
|
APP.store.dispatch(setVideoAvailable(available));
|
2017-08-04 08:15:11 +00:00
|
|
|
APP.API.notifyVideoAvailabilityChanged(available);
|
2017-06-28 14:15:46 +00:00
|
|
|
},
|
2016-06-20 21:13:17 +00:00
|
|
|
|
2016-10-05 21:33:09 +00:00
|
|
|
/**
|
|
|
|
* Disconnect from the conference and optionally request user feedback.
|
|
|
|
* @param {boolean} [requestFeedback=false] if user feedback should be
|
|
|
|
* requested
|
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
hangup(requestFeedback = false) {
|
2017-01-23 18:07:08 +00:00
|
|
|
eventEmitter.emit(JitsiMeetConferenceEvents.BEFORE_HANGUP);
|
2019-01-01 21:19:34 +00:00
|
|
|
|
2019-01-26 20:53:11 +00:00
|
|
|
this._stopProxyConnection();
|
|
|
|
|
2019-01-01 21:19:34 +00:00
|
|
|
APP.store.dispatch(destroyLocalTracks());
|
|
|
|
this._localTracksInitialized = false;
|
|
|
|
this.localVideo = null;
|
|
|
|
this.localAudio = null;
|
2017-06-05 18:19:25 +00:00
|
|
|
|
2018-05-01 19:42:08 +00:00
|
|
|
// Remove unnecessary event listeners from firing callbacks.
|
2018-07-09 18:46:26 +00:00
|
|
|
if (this.deviceChangeListener) {
|
|
|
|
JitsiMeetJS.mediaDevices.removeEventListener(
|
|
|
|
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
|
|
|
|
this.deviceChangeListener);
|
|
|
|
}
|
2018-05-01 19:42:08 +00:00
|
|
|
|
2019-01-01 21:19:34 +00:00
|
|
|
APP.UI.removeAllListeners();
|
|
|
|
APP.remoteControl.removeAllListeners();
|
|
|
|
|
2017-08-07 16:20:44 +00:00
|
|
|
let requestFeedbackPromise;
|
|
|
|
|
|
|
|
if (requestFeedback) {
|
|
|
|
requestFeedbackPromise
|
|
|
|
= APP.store.dispatch(maybeOpenFeedbackDialog(room))
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2017-08-07 16:20:44 +00:00
|
|
|
// false because the thank you dialog shouldn't be displayed
|
|
|
|
.catch(() => Promise.resolve(false));
|
|
|
|
} else {
|
|
|
|
requestFeedbackPromise = Promise.resolve(true);
|
|
|
|
}
|
|
|
|
|
2016-10-12 21:30:44 +00:00
|
|
|
// All promises are returning Promise.resolve to make Promise.all to
|
|
|
|
// be resolved when both Promises are finished. Otherwise Promise.all
|
|
|
|
// will reject on first rejected Promise and we can redirect the page
|
|
|
|
// before all operations are done.
|
|
|
|
Promise.all([
|
|
|
|
requestFeedbackPromise,
|
2018-08-01 20:37:15 +00:00
|
|
|
this.leaveRoomAndDisconnect()
|
2016-10-20 19:19:55 +00:00
|
|
|
]).then(values => {
|
2019-01-18 00:04:35 +00:00
|
|
|
this._room = undefined;
|
|
|
|
room = undefined;
|
|
|
|
|
2016-10-12 21:30:44 +00:00
|
|
|
APP.API.notifyReadyToClose();
|
2016-10-20 19:19:55 +00:00
|
|
|
maybeRedirectToWelcomePage(values[0]);
|
2016-10-12 21:30:44 +00:00
|
|
|
});
|
2017-01-12 21:53:17 +00:00
|
|
|
},
|
|
|
|
|
2018-08-01 20:37:15 +00:00
|
|
|
/**
|
|
|
|
* Leaves the room and calls JitsiConnection.disconnect.
|
|
|
|
*
|
|
|
|
* @returns {Promise}
|
|
|
|
*/
|
|
|
|
leaveRoomAndDisconnect() {
|
|
|
|
APP.store.dispatch(conferenceWillLeave(room));
|
|
|
|
|
2019-01-01 21:19:34 +00:00
|
|
|
return room.leave()
|
2019-01-18 00:04:35 +00:00
|
|
|
.then(disconnect, disconnect);
|
2018-08-01 20:37:15 +00:00
|
|
|
},
|
|
|
|
|
2017-01-12 21:53:17 +00:00
|
|
|
/**
|
|
|
|
* Changes the email for the local user
|
|
|
|
* @param email {string} the new email
|
|
|
|
*/
|
|
|
|
changeLocalEmail(email = '') {
|
2017-10-06 17:52:23 +00:00
|
|
|
const localParticipant = getLocalParticipant(APP.store.getState());
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
const formattedEmail = String(email).trim();
|
2017-01-12 21:53:17 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
if (formattedEmail === localParticipant.email) {
|
2017-01-12 21:53:17 +00:00
|
|
|
return;
|
|
|
|
}
|
2017-03-23 18:01:33 +00:00
|
|
|
|
2017-10-06 17:52:23 +00:00
|
|
|
const localId = localParticipant.id;
|
2017-03-23 18:01:33 +00:00
|
|
|
|
|
|
|
APP.store.dispatch(participantUpdated({
|
Associate remote participant w/ JitsiConference (_UPDATED)
The commit message of "Associate remote participant w/ JitsiConference
(_JOINED)" explains the motivation for this commit.
Practically, _JOINED and _LEFT combined with "Remove remote participants
who are no longer of interest" should alleviate the problem with
multiplying remote participants to an acceptable level of annoyance.
Technically though, a remote participant cannot be identified by an ID
only. The ID is (somewhat) "unique" in the context of a single
JitsiConference instance. So in order to not have to scratch our heads
over an obscure corner, racing case, it's better to always identify
remote participants by the pair id-conference. Unfortunately, that's a
bit of a high order given the existing source code. So I've implemented
the cases which are the easiest so that new source code written with
participantUpdated is more likely to identify a remote participant with
the pair id-conference.
Additionally, the commit "Reduce direct read access to the
features/base/participants redux state" brings more control back to the
functions of the feature base/participants so that one day we can (if we
choose to) do something like, for example:
If getParticipants is called with a conference, it returns the
participants from features/base/participants who are associated with the
specified conference. If no conference is specified in the function
call, then default to the conference which is the primary focus of the
app at the time of the function call. Added to the above, this should
allow us to further reduce the cases in which we're identifying remote
participants by id only and get us even closer to a more "predictable"
behavior in corner, racing cases.
2018-05-22 21:47:43 +00:00
|
|
|
// XXX Only the local participant is allowed to update without
|
|
|
|
// stating the JitsiConference instance (i.e. participant property
|
|
|
|
// `conference` for a remote participant) because the local
|
|
|
|
// participant is uniquely identified by the very fact that there is
|
|
|
|
// only one local participant.
|
|
|
|
|
2017-03-23 18:01:33 +00:00
|
|
|
id: localId,
|
|
|
|
local: true,
|
2017-12-19 23:11:54 +00:00
|
|
|
email: formattedEmail
|
2017-03-23 18:01:33 +00:00
|
|
|
}));
|
2017-01-12 21:53:17 +00:00
|
|
|
|
2018-04-12 19:58:20 +00:00
|
|
|
APP.store.dispatch(updateSettings({
|
|
|
|
email: formattedEmail
|
|
|
|
}));
|
2018-06-18 09:19:07 +00:00
|
|
|
APP.API.notifyEmailChanged(localId, {
|
|
|
|
email: formattedEmail
|
|
|
|
});
|
2017-10-12 23:02:29 +00:00
|
|
|
sendData(commands.EMAIL, formattedEmail);
|
2017-01-12 21:53:17 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes the avatar url for the local user
|
|
|
|
* @param url {string} the new url
|
|
|
|
*/
|
|
|
|
changeLocalAvatarUrl(url = '') {
|
2017-10-06 17:52:23 +00:00
|
|
|
const { avatarURL, id } = getLocalParticipant(APP.store.getState());
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
const formattedUrl = String(url).trim();
|
2017-01-12 21:53:17 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
if (formattedUrl === avatarURL) {
|
2017-01-12 21:53:17 +00:00
|
|
|
return;
|
|
|
|
}
|
2017-03-23 18:01:33 +00:00
|
|
|
|
|
|
|
APP.store.dispatch(participantUpdated({
|
Associate remote participant w/ JitsiConference (_UPDATED)
The commit message of "Associate remote participant w/ JitsiConference
(_JOINED)" explains the motivation for this commit.
Practically, _JOINED and _LEFT combined with "Remove remote participants
who are no longer of interest" should alleviate the problem with
multiplying remote participants to an acceptable level of annoyance.
Technically though, a remote participant cannot be identified by an ID
only. The ID is (somewhat) "unique" in the context of a single
JitsiConference instance. So in order to not have to scratch our heads
over an obscure corner, racing case, it's better to always identify
remote participants by the pair id-conference. Unfortunately, that's a
bit of a high order given the existing source code. So I've implemented
the cases which are the easiest so that new source code written with
participantUpdated is more likely to identify a remote participant with
the pair id-conference.
Additionally, the commit "Reduce direct read access to the
features/base/participants redux state" brings more control back to the
functions of the feature base/participants so that one day we can (if we
choose to) do something like, for example:
If getParticipants is called with a conference, it returns the
participants from features/base/participants who are associated with the
specified conference. If no conference is specified in the function
call, then default to the conference which is the primary focus of the
app at the time of the function call. Added to the above, this should
allow us to further reduce the cases in which we're identifying remote
participants by id only and get us even closer to a more "predictable"
behavior in corner, racing cases.
2018-05-22 21:47:43 +00:00
|
|
|
// XXX Only the local participant is allowed to update without
|
|
|
|
// stating the JitsiConference instance (i.e. participant property
|
|
|
|
// `conference` for a remote participant) because the local
|
|
|
|
// participant is uniquely identified by the very fact that there is
|
|
|
|
// only one local participant.
|
|
|
|
|
2017-10-06 17:52:23 +00:00
|
|
|
id,
|
2017-03-23 18:01:33 +00:00
|
|
|
local: true,
|
2017-10-12 23:02:29 +00:00
|
|
|
avatarURL: formattedUrl
|
2017-03-23 18:01:33 +00:00
|
|
|
}));
|
2017-01-12 21:53:17 +00:00
|
|
|
|
2018-04-12 19:58:20 +00:00
|
|
|
APP.store.dispatch(updateSettings({
|
|
|
|
avatarURL: formattedUrl
|
|
|
|
}));
|
2017-01-12 21:53:17 +00:00
|
|
|
sendData(commands.AVATAR_URL, url);
|
2016-12-09 23:15:04 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends a message via the data channel.
|
2017-01-18 00:16:18 +00:00
|
|
|
* @param {string} to the id of the endpoint that should receive the
|
|
|
|
* message. If "" - the message will be sent to all participants.
|
|
|
|
* @param {object} payload the payload of the message.
|
2016-12-09 23:15:04 +00:00
|
|
|
* @throws NetworkError or InvalidStateError or Error if the operation
|
|
|
|
* fails.
|
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
sendEndpointMessage(to, payload) {
|
2016-12-09 23:15:04 +00:00
|
|
|
room.sendEndpointMessage(to, payload);
|
2017-01-23 18:07:08 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds new listener.
|
|
|
|
* @param {String} eventName the name of the event
|
|
|
|
* @param {Function} listener the listener.
|
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
addListener(eventName, listener) {
|
2017-01-23 18:07:08 +00:00
|
|
|
eventEmitter.addListener(eventName, listener);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes listener.
|
|
|
|
* @param {String} eventName the name of the event that triggers the
|
|
|
|
* listener
|
|
|
|
* @param {Function} listener the listener.
|
|
|
|
*/
|
2017-04-11 19:40:03 +00:00
|
|
|
removeListener(eventName, listener) {
|
2017-01-23 18:07:08 +00:00
|
|
|
eventEmitter.removeListener(eventName, listener);
|
2017-03-21 17:14:13 +00:00
|
|
|
},
|
|
|
|
|
2017-03-23 17:45:51 +00:00
|
|
|
/**
|
|
|
|
* Changes the display name for the local user
|
|
|
|
* @param nickname {string} the new display name
|
|
|
|
*/
|
|
|
|
changeLocalDisplayName(nickname = '') {
|
2019-01-15 11:28:07 +00:00
|
|
|
const formattedNickname = getNormalizedDisplayName(nickname);
|
2017-10-06 17:52:23 +00:00
|
|
|
const { id, name } = getLocalParticipant(APP.store.getState());
|
2017-03-23 17:45:51 +00:00
|
|
|
|
2017-10-06 17:52:23 +00:00
|
|
|
if (formattedNickname === name) {
|
2017-03-23 17:45:51 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-05-03 23:57:52 +00:00
|
|
|
APP.store.dispatch(participantUpdated({
|
Associate remote participant w/ JitsiConference (_UPDATED)
The commit message of "Associate remote participant w/ JitsiConference
(_JOINED)" explains the motivation for this commit.
Practically, _JOINED and _LEFT combined with "Remove remote participants
who are no longer of interest" should alleviate the problem with
multiplying remote participants to an acceptable level of annoyance.
Technically though, a remote participant cannot be identified by an ID
only. The ID is (somewhat) "unique" in the context of a single
JitsiConference instance. So in order to not have to scratch our heads
over an obscure corner, racing case, it's better to always identify
remote participants by the pair id-conference. Unfortunately, that's a
bit of a high order given the existing source code. So I've implemented
the cases which are the easiest so that new source code written with
participantUpdated is more likely to identify a remote participant with
the pair id-conference.
Additionally, the commit "Reduce direct read access to the
features/base/participants redux state" brings more control back to the
functions of the feature base/participants so that one day we can (if we
choose to) do something like, for example:
If getParticipants is called with a conference, it returns the
participants from features/base/participants who are associated with the
specified conference. If no conference is specified in the function
call, then default to the conference which is the primary focus of the
app at the time of the function call. Added to the above, this should
allow us to further reduce the cases in which we're identifying remote
participants by id only and get us even closer to a more "predictable"
behavior in corner, racing cases.
2018-05-22 21:47:43 +00:00
|
|
|
// XXX Only the local participant is allowed to update without
|
|
|
|
// stating the JitsiConference instance (i.e. participant property
|
|
|
|
// `conference` for a remote participant) because the local
|
|
|
|
// participant is uniquely identified by the very fact that there is
|
|
|
|
// only one local participant.
|
|
|
|
|
2017-10-06 17:52:23 +00:00
|
|
|
id,
|
2017-05-03 23:57:52 +00:00
|
|
|
local: true,
|
|
|
|
name: formattedNickname
|
|
|
|
}));
|
|
|
|
|
2018-04-12 19:58:20 +00:00
|
|
|
APP.store.dispatch(updateSettings({
|
|
|
|
displayName: formattedNickname
|
|
|
|
}));
|
|
|
|
|
2017-12-05 03:27:17 +00:00
|
|
|
APP.API.notifyDisplayNameChanged(id, {
|
|
|
|
displayName: formattedNickname,
|
|
|
|
formattedDisplayName:
|
|
|
|
appendSuffix(
|
|
|
|
formattedNickname,
|
|
|
|
interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME)
|
|
|
|
});
|
|
|
|
|
2017-04-18 15:35:14 +00:00
|
|
|
if (room) {
|
2017-10-06 17:52:23 +00:00
|
|
|
APP.UI.changeDisplayName(id, formattedNickname);
|
2017-04-18 15:35:14 +00:00
|
|
|
}
|
2017-06-22 21:28:57 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the desktop sharing source id or undefined if the desktop sharing
|
|
|
|
* is not active at the moment.
|
|
|
|
*
|
|
|
|
* @returns {string|undefined} - The source id. If the track is not desktop
|
|
|
|
* track or the source id is not available, undefined will be returned.
|
|
|
|
*/
|
|
|
|
getDesktopSharingSourceId() {
|
2017-08-18 11:30:30 +00:00
|
|
|
return this.localVideo.sourceId;
|
2017-07-07 22:45:24 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the desktop sharing source type or undefined if the desktop
|
|
|
|
* sharing is not active at the moment.
|
|
|
|
*
|
|
|
|
* @returns {'screen'|'window'|undefined} - The source type. If the track is
|
|
|
|
* not desktop track or the source type is not available, undefined will be
|
|
|
|
* returned.
|
|
|
|
*/
|
|
|
|
getDesktopSharingSourceType() {
|
2017-08-18 11:30:30 +00:00
|
|
|
return this.localVideo.sourceType;
|
2017-08-04 08:15:11 +00:00
|
|
|
},
|
|
|
|
|
2019-01-26 20:53:11 +00:00
|
|
|
/**
|
|
|
|
* Callback invoked by the external api create or update a direct connection
|
|
|
|
* from the local client to an external client.
|
|
|
|
*
|
|
|
|
* @param {Object} event - The object containing information that should be
|
|
|
|
* passed to the {@code ProxyConnectionService}.
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
onProxyConnectionEvent(event) {
|
|
|
|
if (!this._proxyConnection) {
|
|
|
|
this._proxyConnection = new JitsiMeetJS.ProxyConnectionService({
|
2019-03-13 18:15:56 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Pass the {@code JitsiConnection} instance which will be used
|
|
|
|
* to fetch TURN credentials.
|
|
|
|
*/
|
|
|
|
jitsiConnection: APP.connection,
|
|
|
|
|
2019-01-26 20:53:11 +00:00
|
|
|
/**
|
|
|
|
* The proxy connection feature is currently tailored towards
|
|
|
|
* taking a proxied video stream and showing it as a local
|
|
|
|
* desktop screen.
|
|
|
|
*/
|
|
|
|
convertVideoToDesktop: true,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Callback invoked to pass messages from the local client back
|
|
|
|
* out to the external client.
|
|
|
|
*
|
|
|
|
* @param {string} peerJid - The jid of the intended recipient
|
|
|
|
* of the message.
|
|
|
|
* @param {Object} data - The message that should be sent. For
|
|
|
|
* screensharing this is an iq.
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
onSendMessage: (peerJid, data) =>
|
|
|
|
APP.API.sendProxyConnectionEvent({
|
|
|
|
data,
|
|
|
|
to: peerJid
|
|
|
|
}),
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Callback invoked when the remote peer of the proxy connection
|
|
|
|
* has provided a video stream, intended to be used as a local
|
|
|
|
* desktop stream.
|
|
|
|
*
|
|
|
|
* @param {JitsiLocalTrack} remoteProxyStream - The media
|
|
|
|
* stream to use as a local desktop stream.
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
onRemoteStream: desktopStream => {
|
|
|
|
if (desktopStream.videoType !== 'desktop') {
|
|
|
|
logger.warn('Received a non-desktop stream to proxy.');
|
|
|
|
desktopStream.dispose();
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.toggleScreenSharing(undefined, { desktopStream });
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
this._proxyConnection.processMessage(event);
|
|
|
|
},
|
|
|
|
|
2017-08-04 08:15:11 +00:00
|
|
|
/**
|
|
|
|
* Sets the video muted status.
|
|
|
|
*
|
|
|
|
* @param {boolean} muted - New muted status.
|
|
|
|
*/
|
|
|
|
setVideoMuteStatus(muted) {
|
2017-08-18 11:30:30 +00:00
|
|
|
APP.UI.setVideoMuted(this.getMyUserId(), muted);
|
|
|
|
APP.API.notifyVideoMutedStatusChanged(muted);
|
2017-08-04 08:15:11 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the audio muted status.
|
|
|
|
*
|
|
|
|
* @param {boolean} muted - New muted status.
|
|
|
|
*/
|
|
|
|
setAudioMuteStatus(muted) {
|
2017-08-18 11:30:30 +00:00
|
|
|
APP.UI.setAudioMuted(this.getMyUserId(), muted);
|
|
|
|
APP.API.notifyAudioMutedStatusChanged(muted);
|
2018-01-19 22:19:55 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Dispatches the passed in feedback for submission. The submitted score
|
|
|
|
* should be a number inclusively between 1 through 5, or -1 for no score.
|
|
|
|
*
|
|
|
|
* @param {number} score - a number between 1 and 5 (inclusive) or -1 for no
|
|
|
|
* score.
|
|
|
|
* @param {string} message - An optional message to attach to the feedback
|
|
|
|
* in addition to the score.
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
submitFeedback(score = -1, message = '') {
|
|
|
|
if (score === -1 || (score >= 1 && score <= 5)) {
|
|
|
|
APP.store.dispatch(submitFeedback(score, message, room));
|
|
|
|
}
|
2019-01-26 20:53:11 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Terminates any proxy screensharing connection that is active.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
_stopProxyConnection() {
|
|
|
|
if (this._proxyConnection) {
|
|
|
|
this._proxyConnection.stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
this._proxyConnection = null;
|
2017-08-18 11:30:30 +00:00
|
|
|
}
|
2016-07-08 01:44:04 +00:00
|
|
|
};
|