Compare commits

...

8 Commits

Author SHA1 Message Date
Дамян Минков
c2d2eace9f fix: Fixes destroying main room when breakout rooms are enabled.
By the time the timer is fired the room maybe recreated (destroyed and created again) and we do not want to destroy it again. The timer was keeping a reference to the old room and is destroying it without notifying the future occupants(jicofo).
Drop save, as we do not need it before destroying, check lobby.
2021-12-16 09:41:52 -06:00
Дамян Минков
ea63b77326 fix: Fixes emitting conference left event in iframeAPI.
Moving to breakout room and back in main room do not fire videoConferenceLeft events.
And the videoConferenceJoined was reporting wrong conference name on join.
2021-12-16 09:41:38 -06:00
Vlad Piersec
88def9c1e3 fix(Prejoin): Allow changing 'Enable pre meeting screen' option while prejoin screen visible 2021-12-15 14:51:31 +02:00
Дамян Минков
bc77fafb50 fix: Disabled all UI for hiding self view and uses just config. (#10619)
* fix: Disabled all UI for hiding self view and uses config.

This allows use of the feature through config overwrite only.

* squash: Fix lint errors by cleaning up more.

* squash: Drop disableSelfView from settings.

* squash: Drops notification.
2021-12-14 16:25:12 -06:00
Дамян Минков
e3fbbd7212 fix: Use default remote display name in speaker stats when one is missing. 2021-12-14 09:40:44 -06:00
Дамян Минков
ada04a9495 fix: Respects disable reactions moderation flag for popups. 2021-12-14 09:40:37 -06:00
Дамян Минков
a723dbcd97 fix: Fixes disable moderation sounds in meeting. (#10604)
* fix: Fixes disable moderation sounds in meeting.

Moderators in the meeting were sending presence update after one moderator turn it on, which even my result a inconsistent state and flipping the state between moderators several times.

* squash: Adds option to disable reaction moderation.
2021-12-13 13:53:11 -06:00
Horatiu Muresan
b15316eb6f feat(profile-settings): Hide email field under profile settings (#10603) 2021-12-13 17:35:19 +02:00
24 changed files with 104 additions and 199 deletions

View File

@@ -80,6 +80,9 @@ var config = {
// Disables the reactions feature.
// disableReactions: true,
// Disables the reactions moderation feature.
// disableReactionsModeration: false,
// Disables polls feature.
// disablePolls: false,
@@ -484,6 +487,9 @@ var config = {
// Disables profile and the edit of all fields from the profile settings (display name and email)
// disableProfile: false,
// Hides the email section under profile settings.
// hideEmailInSettings: false,
// Whether or not some features are checked based on token.
// enableFeaturesBasedOnToken: false,

View File

@@ -48,7 +48,7 @@
}
.profile-edit-field {
flex: 1;
flex: .5;
}
.settings-sub-pane {
flex: 1;

View File

@@ -670,15 +670,17 @@ export function setFollowMe(enabled: boolean) {
* Enables or disables the Mute reaction sounds feature.
*
* @param {boolean} muted - Whether or not reaction sounds should be muted for all participants.
* @param {boolean} updateBackend - Whether or not the moderator should notify all participants for the new setting.
* @returns {{
* type: SET_START_REACTIONS_MUTED,
* muted: boolean
* }}
*/
export function setStartReactionsMuted(muted: boolean) {
export function setStartReactionsMuted(muted: boolean, updateBackend: boolean = false) {
return {
type: SET_START_REACTIONS_MUTED,
muted
muted,
updateBackend
};
}

View File

@@ -108,6 +108,7 @@ export default [
'disablePolls',
'disableProfile',
'disableReactions',
'disableReactionsModeration',
'disableRecordAudioNotification',
'disableRemoteControl',
'disableRemoteMute',
@@ -165,6 +166,7 @@ export default [
'hideConferenceTimer',
'hiddenDomain',
'hideAddRoomButton',
'hideEmailInSettings',
'hideLobbyButton',
'hosts',
'iAmRecorder',

View File

@@ -125,12 +125,6 @@ function _setConfig({ dispatch, getState }, next, action) {
}));
}
if (action.config.disableSelfView) {
dispatch(updateSettings({
disableSelfView: true
}));
}
dispatch(updateConfig(config));
// FIXME On Web we rely on the global 'config' variable which gets altered

View File

@@ -21,7 +21,6 @@ const DEFAULT_STATE = {
disableCallIntegration: undefined,
disableCrashReporting: undefined,
disableP2P: undefined,
disableSelfView: false,
displayName: undefined,
email: undefined,
localFlipX: true,

View File

@@ -233,7 +233,9 @@ export function moveToRoom(roomId?: string) {
const isVideoMuted = isLocalCameraTrackMuted(localTracks);
try {
await APP.conference.leaveRoom(false /* doDisconnect */);
// all places we fire notifyConferenceLeft we pass the room name from APP.conference
await APP.conference.leaveRoom(false /* doDisconnect */).then(
() => APP.API.notifyConferenceLeft(APP.conference.roomName));
} catch (error) {
logger.warn('APP.conference.leaveRoom() rejected with:', error);

View File

@@ -88,11 +88,12 @@ MiddlewareRegistry.register(store => next => action => {
case CONFERENCE_JOINED: {
const state = store.getState();
const { defaultLocalDisplayName } = state['features/base/config'];
const { room } = state['features/base/conference'];
const { loadableAvatarUrl, name, id } = getLocalParticipant(state);
// we use APP.conference.roomName as we do not update state['features/base/conference'].room when
// moving between rooms in case of breakout rooms and it stays always with the name of the main room
APP.API.notifyConferenceJoined(
room,
APP.conference.roomName,
id,
{
displayName: name,

View File

@@ -90,7 +90,7 @@ export function updateRemoteParticipantsOnLeave(store: Object, participantId: ?s
* @returns {boolean}
*/
export function getDisableSelfView(state: Object) {
const { disableSelfView } = state['features/base/settings'];
const { disableSelfView } = state['features/base/config'];
const participantsCount = getParticipantCount(state);
return participantsCount === 1 ? false : disableSelfView;

View File

@@ -30,7 +30,7 @@ StateListenerRegistry.register(
/* selector */ state => {
return {
numberOfParticipants: getParticipantCountWithFake(state),
disableSelfView: state['features/base/settings'].disableSelfView
disableSelfView: state['features/base/config'].disableSelfView
};
},
/* listener */ (currentState, store) => {

View File

@@ -1,40 +1,3 @@
/* @flow */
import { CONFERENCE_JOINED } from '../base/conference';
import { MiddlewareRegistry } from '../base/redux';
import { openSettingsDialog, SETTINGS_TABS } from '../settings';
import {
showNotification
} from './actions';
import { NOTIFICATION_TIMEOUT_TYPE } from './constants';
import './middleware.any';
/**
* Middleware that captures actions to display notifications.
*
* @param {Store} store - The redux store.
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case CONFERENCE_JOINED: {
const { dispatch, getState } = store;
const { disableSelfView } = getState()['features/base/settings'];
if (disableSelfView) {
dispatch(showNotification({
titleKey: 'notify.selfViewTitle',
customActionNameKey: [ 'settings.title' ],
customActionHandler: [ () =>
dispatch(openSettingsDialog(SETTINGS_TABS.PROFILE))
]
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
}
break;
}
}
return next(action);
});

View File

@@ -15,9 +15,9 @@ export const PREJOIN_INITIALIZED = 'PREJOIN_INITIALIZED';
export const SET_DEVICE_STATUS = 'SET_DEVICE_STATUS';
/**
* Action type to set the visibility of the prejoin page for the future.
* Action type to mark the fact that the 'skip prejoin' option was modified this session.
*/
export const SET_SKIP_PREJOIN = 'SET_SKIP_PREJOIN';
export const SET_SKIP_PREJOIN_CHANGING = 'SET_SKIP_PREJOIN_CHANGING';
/**
* Action type to set the visibility of the prejoin page when client is forcefully reloaded.

View File

@@ -8,7 +8,6 @@ import { v4 as uuidv4 } from 'uuid';
import { getDialOutStatusUrl, getDialOutUrl, updateConfig } from '../base/config';
import { browser, createLocalTrack } from '../base/lib-jitsi-meet';
import { isVideoMutedByUser, MEDIA_TYPE } from '../base/media';
import { updateSettings } from '../base/settings';
import {
createLocalTracksF,
getLocalAudioTrack,
@@ -28,7 +27,7 @@ import {
SET_DIALOUT_NUMBER,
SET_DIALOUT_STATUS,
SET_PREJOIN_DISPLAY_NAME_REQUIRED,
SET_SKIP_PREJOIN,
SET_SKIP_PREJOIN_CHANGING,
SET_SKIP_PREJOIN_RELOAD,
SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
SET_PRECALL_TEST_RESULTS,
@@ -228,15 +227,10 @@ export function joinConference(options?: Object, ignoreJoiningInProgress: boolea
}
const state = getState();
const { userSelectedSkipPrejoin } = state['features/prejoin'];
let localTracks = getLocalTracks(state['features/base/tracks']);
options && dispatch(updateConfig(options));
userSelectedSkipPrejoin && dispatch(updateSettings({
userSelectedSkipPrejoin
}));
// Do not signal audio/video tracks if the user joins muted.
for (const track of localTracks) {
// Always add the audio track on Safari because of a known issue where audio playout doesn't happen
@@ -484,14 +478,15 @@ export function setDialOutNumber(value: string) {
}
/**
* Sets the visibility of the prejoin page for future uses.
* Sets a flag which signals that the option to skip the prejoin
* page on join has been modified during this session.
*
* @param {boolean} value - The visibility value.
* @returns {Object}
*/
export function setSkipPrejoin(value: boolean) {
export function setSkipPrejoinIsChanging(value: boolean) {
return {
type: SET_SKIP_PREJOIN,
type: SET_SKIP_PREJOIN_CHANGING,
value
};
}

View File

@@ -36,16 +36,6 @@ export function isDisplayNameRequired(state: Object): boolean {
|| state['features/base/config'].requireDisplayName;
}
/**
* Selector for determining if the user has chosen to skip prejoin page.
*
* @param {Object} state - The state of the app.
* @returns {boolean}
*/
export function isPrejoinSkipped(state: Object) {
return state['features/prejoin'].userSelectedSkipPrejoin;
}
/**
* Returns the text for the prejoin status bar.
*
@@ -160,6 +150,12 @@ export function isPrejoinPageEnabled(state: Object): boolean {
* @returns {boolean}
*/
export function isPrejoinPageVisible(state: Object): boolean {
// If the user has changed the setting for prejoin visibility on start
// let the visibility be controlled only by the 'showPrejoin' flag.
if (state['features/prejoin'].skipPrejoinChanging) {
return state['features/prejoin']?.showPrejoin;
}
return isPrejoinPageEnabled(state) && state['features/prejoin']?.showPrejoin;
}

View File

@@ -11,7 +11,7 @@ import {
SET_PREJOIN_DEVICE_ERRORS,
SET_PREJOIN_DISPLAY_NAME_REQUIRED,
SET_PREJOIN_PAGE_VISIBILITY,
SET_SKIP_PREJOIN,
SET_SKIP_PREJOIN_CHANGING,
SET_SKIP_PREJOIN_RELOAD
} from './actionTypes';
@@ -30,9 +30,9 @@ const DEFAULT_STATE = {
name: '',
rawError: '',
showPrejoin: true,
skipPrejoinChanging: false,
skipPrejoinOnReload: false,
showJoinByPhoneDialog: false,
userSelectedSkipPrejoin: false
showJoinByPhoneDialog: false
};
/**
@@ -59,10 +59,10 @@ ReducerRegistry.register(
...state,
joiningInProgress: action.value
};
case SET_SKIP_PREJOIN: {
case SET_SKIP_PREJOIN_CHANGING: {
return {
...state,
userSelectedSkipPrejoin: action.value
skipPrejoinChanging: action.value
};
}

View File

@@ -4,7 +4,11 @@ import { batch } from 'react-redux';
import { createReactionSoundsDisabledEvent, sendAnalytics } from '../analytics';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
import { CONFERENCE_WILL_JOIN, setStartReactionsMuted } from '../base/conference';
import {
CONFERENCE_WILL_JOIN,
SET_START_REACTIONS_MUTED,
setStartReactionsMuted
} from '../base/conference';
import {
getParticipantById,
getParticipantCount,
@@ -49,11 +53,6 @@ import {
import logger from './logger';
import { RAISE_HAND_SOUND_FILE } from './sounds';
import './subscriber';
declare var APP: Object;
/**
* Middleware which intercepts Reactions actions to handle changes to the
* visibility timeout of the Reactions.
@@ -171,6 +170,18 @@ MiddlewareRegistry.register(store => next => action => {
break;
}
// Settings changed for mute reactions in the meeting
case SET_START_REACTIONS_MUTED: {
const state = getState();
const { conference } = state['features/base/conference'];
const { muted, updateBackend } = action;
if (conference && isLocalParticipantModerator(state) && updateBackend) {
conference.sendCommand(MUTE_REACTIONS_COMMAND, { attributes: { startReactionsMuted: Boolean(muted) } });
}
break;
}
case SETTINGS_UPDATED: {
const { soundsReactions } = getState()['features/base/settings'];
@@ -183,13 +194,14 @@ MiddlewareRegistry.register(store => next => action => {
case SHOW_SOUNDS_NOTIFICATION: {
const state = getState();
const isModerator = isLocalParticipantModerator(state);
const { disableReactionsModeration } = state['features/base/config'];
const customActions = [ 'notify.reactionSounds' ];
const customFunctions = [ () => dispatch(updateSettings({
soundsReactions: false
})) ];
if (isModerator) {
if (isModerator && !disableReactionsModeration) {
customActions.push('notify.reactionSoundsForAll');
customFunctions.push(() => batch(() => {
dispatch(setStartReactionsMuted(true));

View File

@@ -1,45 +0,0 @@
// @flow
import { getCurrentConference } from '../base/conference';
import { isLocalParticipantModerator } from '../base/participants';
import { StateListenerRegistry } from '../base/redux';
import { MUTE_REACTIONS_COMMAND } from './constants';
/**
* Subscribes to changes to the Mute Reaction Sounds setting for the local participant to
* notify remote participants of current user interface status.
* Changing newSelectedValue param to off, when feature is turned of so we can
* notify all listeners.
*/
StateListenerRegistry.register(
/* selector */ state => state['features/base/conference'].startReactionsMuted,
/* listener */ (newSelectedValue, store) => _sendMuteReactionsCommand(newSelectedValue || false, store));
/**
* Sends the mute-reactions command, when a local property change occurs.
*
* @param {*} newSelectedValue - The changed selected value from the selector.
* @param {Object} store - The redux store.
* @private
* @returns {void}
*/
function _sendMuteReactionsCommand(newSelectedValue, store) {
const state = store.getState();
const conference = getCurrentConference(state);
if (!conference) {
return;
}
// Only a moderator is allowed to send commands.
if (!isLocalParticipantModerator(state)) {
return;
}
conference.sendCommand(
MUTE_REACTIONS_COMMAND,
{ attributes: { startReactionsMuted: Boolean(newSelectedValue) } }
);
}

View File

@@ -10,8 +10,7 @@ import {
import { openDialog } from '../base/dialog';
import { i18next } from '../base/i18n';
import { updateSettings } from '../base/settings';
import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../notifications';
import { setPrejoinPageVisibility } from '../prejoin/actions';
import { setPrejoinPageVisibility, setSkipPrejoinIsChanging } from '../prejoin/actions';
import { setScreenshareFramerate } from '../screen-share/actions';
import {
@@ -26,8 +25,6 @@ import {
getSoundsTabProps
} from './functions';
import { SETTINGS_TABS } from '.';
declare var APP: Object;
/**
@@ -96,9 +93,12 @@ export function submitMoreTab(newState: Object): Function {
if (showPrejoinPage && getState()['features/prejoin']?.showPrejoin) {
dispatch(setPrejoinPageVisibility(false));
}
dispatch(updateSettings({
userSelectedSkipPrejoin: !showPrejoinPage
}));
batch(() => {
dispatch(setSkipPrejoinIsChanging(true));
dispatch(updateSettings({
userSelectedSkipPrejoin: !showPrejoinPage
}));
});
}
if (newState.currentLanguage !== currentState.currentLanguage) {
@@ -129,7 +129,8 @@ export function submitModeratorTab(newState: Object): Function {
if (newState.startReactionsMuted !== currentState.startReactionsMuted) {
batch(() => {
dispatch(setStartReactionsMuted(newState.startReactionsMuted));
// updating settings we want to update and backend (notify the rest of the participants)
dispatch(setStartReactionsMuted(newState.startReactionsMuted, true));
dispatch(updateSettings({ soundsReactions: !newState.startReactionsMuted }));
});
}
@@ -159,19 +160,6 @@ export function submitProfileTab(newState: Object): Function {
if (newState.email !== currentState.email) {
APP.conference.changeLocalEmail(newState.email);
}
if (newState.disableSelfView !== currentState.disableSelfView) {
dispatch(updateSettings({ disableSelfView: newState.disableSelfView }));
if (newState.disableSelfView) {
dispatch(showNotification({
titleKey: 'notify.selfViewTitle',
customActionNameKey: [ 'settings.title' ],
customActionHandler: [ () =>
dispatch(openSettingsDialog(SETTINGS_TABS.PROFILE))
]
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
}
}
};
}

View File

@@ -12,6 +12,11 @@ import { translate } from '../../../base/i18n';
export type Props = {
...$Exact<AbstractDialogTabProps>,
/**
* If set hides the reactions moderation setting.
*/
disableReactionsModeration: boolean,
/**
* Whether or not follow me is currently active (enabled by some other participant).
*/
@@ -142,6 +147,7 @@ class ModeratorTab extends AbstractDialogTab<Props> {
*/
_renderModeratorSettings() {
const {
disableReactionsModeration,
followMeActive,
followMeEnabled,
startAudioMuted,
@@ -171,11 +177,12 @@ class ModeratorTab extends AbstractDialogTab<Props> {
label = { t('settings.followMe') }
name = 'follow-me'
onChange = { this._onFollowMeEnabledChanged } />
<Checkbox
isChecked = { startReactionsMuted }
label = { t('settings.startReactionsMuted') }
name = 'start-reactions-muted'
onChange = { this._onStartReactionsMutedChanged } />
{ !disableReactionsModeration
&& <Checkbox
isChecked = { startReactionsMuted }
label = { t('settings.startReactionsMuted') }
name = 'start-reactions-muted'
onChange = { this._onStartReactionsMutedChanged } /> }
</div>
</div>
);

View File

@@ -1,7 +1,6 @@
// @flow
import Button from '@atlaskit/button/standard-button';
import Checkbox from '@atlaskit/checkbox';
import { FieldTextStateless } from '@atlaskit/field-text';
import React from 'react';
@@ -33,11 +32,6 @@ export type Props = {
*/
authLogin: string,
/**
* Whether or not to hide the self view.
*/
disableSelfView: boolean,
/**
* The display name to display for the local participant.
*/
@@ -53,6 +47,11 @@ export type Props = {
*/
readOnlyName: boolean,
/**
* Whether to hide the email input in the profile settings.
*/
hideEmailInSettings?: boolean,
/**
* Invoked to obtain translated strings.
*/
@@ -83,7 +82,6 @@ class ProfileTab extends AbstractDialogTab<Props> {
this._onAuthToggle = this._onAuthToggle.bind(this);
this._onDisplayNameChange = this._onDisplayNameChange.bind(this);
this._onEmailChange = this._onEmailChange.bind(this);
this._onChange = this._onChange.bind(this);
}
_onDisplayNameChange: (Object) => void;
@@ -112,19 +110,6 @@ class ProfileTab extends AbstractDialogTab<Props> {
super._onChange({ email: value });
}
_onChange: (Object) => void;
/**
* Changes the disable self view state.
*
* @param {Object} e - The key event to handle.
*
* @returns {void}
*/
_onChange({ target }) {
super._onChange({ disableSelfView: target.checked });
}
/**
* Implements React's {@link Component#render()}.
*
@@ -135,8 +120,8 @@ class ProfileTab extends AbstractDialogTab<Props> {
const {
authEnabled,
displayName,
disableSelfView,
email,
hideEmailInSettings,
readOnlyName,
t
} = this.props;
@@ -157,7 +142,7 @@ class ProfileTab extends AbstractDialogTab<Props> {
type = 'text'
value = { displayName } />
</div>
<div className = 'profile-edit-field'>
{!hideEmailInSettings && <div className = 'profile-edit-field'>
<FieldTextStateless
compact = { true }
id = 'setEmail'
@@ -167,14 +152,8 @@ class ProfileTab extends AbstractDialogTab<Props> {
shouldFitContainer = { true }
type = 'text'
value = { email } />
</div>
</div>}
</div>
<br />
<Checkbox
isChecked = { disableSelfView }
label = { t('videothumbnail.hideSelfView') }
name = 'disableSelfView'
onChange = { this._onChange } />
{ authEnabled && this._renderAuth() }
</div>
);

View File

@@ -120,6 +120,7 @@ export function getModeratorTabProps(stateful: Object | Function) {
startVideoMutedPolicy,
startReactionsMuted
} = state['features/base/conference'];
const { disableReactionsModeration } = state['features/base/config'];
const followMeActive = isFollowMeActive(state);
const configuredTabs = interfaceConfig.SETTINGS_SECTIONS || [];
@@ -131,6 +132,7 @@ export function getModeratorTabProps(stateful: Object | Function) {
// The settings sections to display.
return {
showModeratorSettings,
disableReactionsModeration: Boolean(disableReactionsModeration),
followMeActive: Boolean(conference && followMeActive),
followMeEnabled: Boolean(conference && followMeEnabled),
startReactionsMuted: Boolean(conference && startReactionsMuted),
@@ -155,16 +157,16 @@ export function getProfileTabProps(stateful: Object | Function) {
authLogin,
conference
} = state['features/base/conference'];
const { hideEmailInSettings } = state['features/base/config'];
const localParticipant = getLocalParticipant(state);
const { disableSelfView } = state['features/base/settings'];
return {
authEnabled: Boolean(conference && authEnabled),
authLogin,
displayName: localParticipant.name,
disableSelfView: Boolean(disableSelfView),
email: localParticipant.email,
readOnlyName: isNameReadOnly(state)
readOnlyName: isNameReadOnly(state),
hideEmailInSettings
};
}

View File

@@ -22,7 +22,8 @@ const abstractSpeakerStatsList = (speakerStatsItem: Function): Function[] => {
const conference = useSelector(state => state['features/base/conference'].conference);
const speakerStats = useSelector(state => state['features/speaker-stats'].stats);
const localParticipant = useSelector(getLocalParticipant);
const { enableFacialRecognition } = useSelector(state => state['features/base/config']) || {};
const { defaultRemoteDisplayName, enableFacialRecognition } = useSelector(
state => state['features/base/config']) || {};
const { facialExpressions: localFacialExpressions } = useSelector(
state => state['features/facial-recognition']) || {};
@@ -92,7 +93,7 @@ const abstractSpeakerStatsList = (speakerStatsItem: Function): Function[] => {
props.facialExpressions = statsModel.getFacialExpressions();
}
props.showFacialExpressions = enableFacialRecognition;
props.displayName = statsModel.getDisplayName();
props.displayName = statsModel.getDisplayName() || defaultRemoteDisplayName;
props.t = t;
return speakerStatsItem(props);

View File

@@ -19,7 +19,6 @@ import { renderConnectionStatus } from '../../actions.web';
import ConnectionStatusButton from './ConnectionStatusButton';
import FlipLocalVideoButton from './FlipLocalVideoButton';
import HideSelfViewVideoButton from './HideSelfViewVideoButton';
import VideoMenu from './VideoMenu';
@@ -132,7 +131,6 @@ class LocalVideoMenuTriggerButton extends Component<Props> {
: (
<VideoMenu id = 'localVideoMenu'>
<FlipLocalVideoButton onClick = { hidePopover } />
<HideSelfViewVideoButton onClick = { hidePopover } />
{ isMobileBrowser()
&& <ConnectionStatusButton participantId = { _localParticipantId } />
}

View File

@@ -124,6 +124,7 @@ function broadcast_breakout_rooms(room_jid)
main_room._data.is_broadcast_breakout_scheduled = true;
main_room:save(true);
module:add_timer(BROADCAST_ROOMS_INTERVAL, function()
local main_room, main_room_jid = get_main_room(room_jid);
main_room._data.is_broadcast_breakout_scheduled = false;
main_room:save(true);
@@ -374,16 +375,16 @@ function exist_occupants_in_rooms(main_room)
end
function on_occupant_left(event)
local room = event.room;
local room_jid = event.room.jid;
if is_healthcheck_room(room.jid) then
if is_healthcheck_room(room_jid) then
return;
end
local main_room, main_room_jid = get_main_room(room.jid);
local main_room = get_main_room(room_jid);
if main_room._data.breakout_rooms_active and jid_node(event.occupant.jid) ~= 'focus' then
broadcast_breakout_rooms(room.jid);
broadcast_breakout_rooms(room_jid);
end
-- Close the conference if all left for good.
@@ -391,11 +392,13 @@ function on_occupant_left(event)
main_room._data.is_close_all_scheduled = true;
main_room:save(true);
module:add_timer(ROOMS_TTL_IF_ALL_LEFT, function()
-- we need to look up again the room as till the timer is fired, the room maybe already destroyed/recreated
-- and we will have the old instance
local main_room, main_room_jid = get_main_room(room_jid);
if main_room._data.is_close_all_scheduled then
module:log('info', 'Closing conference %s as all left for good.', main_room_jid);
main_room:set_persistent(false);
main_room:save(true);
main_room:destroy(main_room_jid, 'All occupants left.');
main_room:destroy(nil, 'All occupants left.');
end
end)
end