bigbluebutton-Github/bigbluebutton-html5/imports/ui/components/layout/push-layout/pushLayoutEngine.jsx

396 lines
13 KiB
React
Raw Normal View History

import React, { useEffect } from 'react';
2022-09-03 02:18:17 +08:00
import PropTypes from 'prop-types';
import getFromUserSettings from '/imports/ui/services/users-settings';
import { getSettingsSingletonInstance } from '/imports/ui/services/settings';
2022-09-03 02:18:17 +08:00
import MediaService from '/imports/ui/components/media/service';
import { LAYOUT_TYPE, ACTIONS } from '../enums';
import { isMobile } from '../utils';
import { updateSettings } from '/imports/ui/components/settings/service';
import Session from '/imports/ui/services/storage/in-memory';
import usePreviousValue from '/imports/ui/hooks/usePreviousValue';
import useMeeting from '/imports/ui/core/hooks/useMeeting';
import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser';
import useUserChangedLocalSettings from '/imports/ui/services/settings/hooks/useUserChangedLocalSettings';
import useSettings from '/imports/ui/services/settings/hooks/useSettings';
import { SETTINGS } from '/imports/ui/services/settings/enums';
import {
layoutDispatch,
layoutSelectInput,
layoutSelectOutput,
} from '../context';
import { calculatePresentationVideoRate } from './service';
import { useMeetingLayoutUpdater, usePushLayoutUpdater } from './hooks';
2022-09-03 02:18:17 +08:00
const equalDouble = (n1, n2) => {
const precision = 0.01;
return Math.abs(n1 - n2) <= precision;
};
const propTypes = {
cameraWidth: PropTypes.number,
cameraHeight: PropTypes.number,
cameraIsResizing: PropTypes.bool,
cameraPosition: PropTypes.string,
focusedCamera: PropTypes.string,
2022-09-15 23:01:31 +08:00
horizontalPosition: PropTypes.bool,
isMeetingLayoutResizing: PropTypes.bool,
2022-09-03 02:18:17 +08:00
isPresenter: PropTypes.bool,
isModerator: PropTypes.bool,
layoutContextDispatch: PropTypes.func,
meetingLayout: PropTypes.string,
meetingLayoutCameraPosition: PropTypes.string,
meetingLayoutFocusedCamera: PropTypes.string,
meetingLayoutVideoRate: PropTypes.number,
meetingPresentationIsOpen: PropTypes.bool,
meetingLayoutUpdatedAt: PropTypes.number,
presentationIsOpen: PropTypes.bool,
presentationVideoRate: PropTypes.number,
pushLayout: PropTypes.bool,
pushLayoutMeeting: PropTypes.bool,
selectedLayout: PropTypes.string,
setMeetingLayout: PropTypes.func,
setPushLayout: PropTypes.func,
2022-09-03 02:18:17 +08:00
shouldShowScreenshare: PropTypes.bool,
shouldShowExternalVideo: PropTypes.bool,
enforceLayout: PropTypes.string,
2024-01-30 21:03:11 +08:00
setLocalSettings: PropTypes.func.isRequired,
2024-09-12 01:43:45 +08:00
hasMeetingLayout: PropTypes.bool,
2022-09-03 02:18:17 +08:00
};
const PushLayoutEngine = (props) => {
const prevProps = usePreviousValue(props) || {};
const {
cameraWidth,
cameraHeight,
horizontalPosition,
meetingLayout,
meetingLayoutCameraPosition,
meetingLayoutFocusedCamera,
meetingLayoutVideoRate,
meetingPresentationIsOpen,
shouldShowScreenshare,
shouldShowExternalVideo,
enforceLayout,
setLocalSettings,
pushLayoutMeeting,
cameraIsResizing,
cameraPosition,
focusedCamera,
isMeetingLayoutResizing,
isModerator,
isPresenter,
layoutContextDispatch,
meetingLayoutUpdatedAt,
presentationIsOpen,
presentationVideoRate,
pushLayout,
selectedLayout,
setMeetingLayout,
setPushLayout,
2024-09-12 01:43:45 +08:00
hasMeetingLayout,
} = props;
useEffect(() => {
const Settings = getSettingsSingletonInstance();
const changeLayout = LAYOUT_TYPE[getFromUserSettings('bbb_change_layout', null)];
const defaultLayout = LAYOUT_TYPE[getFromUserSettings('bbb_default_layout', null)];
const enforcedLayout = LAYOUT_TYPE[enforceLayout] || null;
2022-09-03 02:18:17 +08:00
Settings.application.selectedLayout = enforcedLayout
|| changeLayout
|| defaultLayout
|| meetingLayout;
let { selectedLayout: actualLayout } = Settings.application;
2022-09-03 02:18:17 +08:00
if (isMobile()) {
actualLayout = actualLayout === 'custom' ? 'smart' : actualLayout;
Settings.application.actualLayout = actualLayout;
2022-09-03 02:18:17 +08:00
}
Session.setItem('isGridEnabled', actualLayout === LAYOUT_TYPE.VIDEO_FOCUS);
2023-07-26 00:20:45 +08:00
2024-01-30 21:03:11 +08:00
Settings.save(setLocalSettings);
2022-09-03 02:18:17 +08:00
const HIDE_PRESENTATION = window.meetingClientSettings.public.layout.hidePresentationOnJoin;
const shouldOpenPresentation = shouldShowScreenshare || shouldShowExternalVideo;
let presentationLastState = !getFromUserSettings('bbb_hide_presentation_on_join', HIDE_PRESENTATION);
presentationLastState = pushLayoutMeeting ? meetingPresentationIsOpen : presentationLastState;
presentationLastState = shouldOpenPresentation || presentationLastState;
MediaService.setPresentationIsOpen(layoutContextDispatch, presentationLastState);
Session.setItem('presentationLastState', presentationLastState);
2022-09-03 02:18:17 +08:00
if (actualLayout === 'custom') {
2022-09-03 02:18:17 +08:00
setTimeout(() => {
layoutContextDispatch({
type: ACTIONS.SET_FOCUSED_CAMERA_ID,
value: meetingLayoutFocusedCamera,
});
layoutContextDispatch({
type: ACTIONS.SET_CAMERA_DOCK_POSITION,
2024-04-02 02:48:15 +08:00
value: meetingLayoutCameraPosition || 'contentTop',
2022-09-03 02:18:17 +08:00
});
if (!equalDouble(meetingLayoutVideoRate, 0)) {
let w; let h;
2022-09-03 02:18:17 +08:00
if (horizontalPosition) {
w = window.innerWidth * meetingLayoutVideoRate;
h = cameraHeight;
} else {
w = cameraWidth;
h = window.innerHeight * meetingLayoutVideoRate;
}
layoutContextDispatch({
type: ACTIONS.SET_CAMERA_DOCK_SIZE,
value: {
width: w,
height: h,
browserWidth: window.innerWidth,
browserHeight: window.innerHeight,
},
2022-09-03 02:18:17 +08:00
});
}
}, 0);
}
2024-09-12 01:43:45 +08:00
}, [hasMeetingLayout]);
2022-09-03 02:18:17 +08:00
useEffect(() => {
2022-09-03 02:18:17 +08:00
const meetingLayoutDidChange = meetingLayout !== prevProps.meetingLayout;
const pushLayoutMeetingDidChange = pushLayoutMeeting !== prevProps.pushLayoutMeeting;
const enforceLayoutDidChange = enforceLayout !== prevProps.enforceLayout;
2022-09-03 02:18:17 +08:00
const shouldSwitchLayout = isPresenter
? meetingLayoutDidChange || enforceLayoutDidChange
: ((meetingLayoutDidChange || pushLayoutMeetingDidChange) && pushLayoutMeeting)
|| enforceLayoutDidChange;
const Settings = getSettingsSingletonInstance();
2022-09-03 02:18:17 +08:00
if (shouldSwitchLayout) {
let contextLayout = enforceLayout || meetingLayout;
2022-09-03 02:18:17 +08:00
if (isMobile()) {
if (contextLayout === 'custom') {
contextLayout = 'smart';
}
2022-09-03 02:18:17 +08:00
}
layoutContextDispatch({
type: ACTIONS.SET_LAYOUT_TYPE,
value: contextLayout,
});
updateSettings({
application: {
...Settings.application,
selectedLayout: contextLayout,
},
2024-01-30 21:03:11 +08:00
}, null, setLocalSettings);
2022-09-03 02:18:17 +08:00
}
if (!enforceLayout && pushLayoutMeetingDidChange) {
2022-09-03 02:18:17 +08:00
updateSettings({
application: {
...Settings.application,
pushLayout: pushLayoutMeeting,
},
2024-01-30 21:03:11 +08:00
}, null, setLocalSettings);
2022-09-03 02:18:17 +08:00
}
if (meetingLayout === 'custom' && selectedLayout === 'custom' && !isPresenter) {
2022-09-03 02:18:17 +08:00
if (meetingLayoutFocusedCamera !== prevProps.meetingLayoutFocusedCamera
|| meetingLayoutUpdatedAt !== prevProps.meetingLayoutUpdatedAt) {
layoutContextDispatch({
type: ACTIONS.SET_FOCUSED_CAMERA_ID,
value: meetingLayoutFocusedCamera,
});
}
if (meetingLayoutCameraPosition !== prevProps.meetingLayoutCameraPosition
|| meetingLayoutUpdatedAt !== prevProps.meetingLayoutUpdatedAt) {
layoutContextDispatch({
type: ACTIONS.SET_CAMERA_DOCK_POSITION,
value: meetingLayoutCameraPosition,
});
}
if (!equalDouble(meetingLayoutVideoRate, prevProps.meetingLayoutVideoRate)
|| meetingLayoutUpdatedAt !== prevProps.meetingLayoutUpdatedAt) {
let w; let h;
2022-09-03 02:18:17 +08:00
if (horizontalPosition) {
w = window.innerWidth * meetingLayoutVideoRate;
h = cameraHeight;
} else {
w = cameraWidth;
h = window.innerHeight * meetingLayoutVideoRate;
}
if (isMeetingLayoutResizing !== prevProps.isMeetingLayoutResizing) {
2022-09-03 02:18:17 +08:00
layoutContextDispatch({
type: ACTIONS.SET_CAMERA_DOCK_IS_RESIZING,
value: isMeetingLayoutResizing,
2022-09-03 02:18:17 +08:00
});
}
layoutContextDispatch({
type: ACTIONS.SET_CAMERA_DOCK_SIZE,
value: {
width: w,
height: h,
browserWidth: window.innerWidth,
browserHeight: window.innerHeight,
},
2022-09-03 02:18:17 +08:00
});
}
if (meetingPresentationIsOpen !== prevProps.meetingPresentationIsOpen
|| meetingLayoutUpdatedAt !== prevProps.meetingLayoutUpdatedAt) {
layoutContextDispatch({
type: ACTIONS.SET_PRESENTATION_IS_OPEN,
value: meetingPresentationIsOpen,
});
}
}
const layoutChanged = presentationIsOpen !== prevProps.presentationIsOpen
|| selectedLayout !== prevProps.selectedLayout
|| cameraIsResizing !== prevProps.cameraIsResizing
|| cameraPosition !== prevProps.cameraPosition
|| focusedCamera !== prevProps.focusedCamera
|| enforceLayout !== prevProps.enforceLayout
2022-09-03 02:18:17 +08:00
|| !equalDouble(presentationVideoRate, prevProps.presentationVideoRate);
// push layout once after presenter toggles
// special case where we set pushLayout to false in all viewers
if (pushLayout !== prevProps.pushLayout) {
if (isModerator) {
setPushLayout(pushLayout);
}
}
// change layout sizes / states
if ((pushLayout && layoutChanged) || pushLayout !== prevProps.pushLayout) {
2022-09-03 02:18:17 +08:00
if (isPresenter) {
setMeetingLayout();
}
}
2023-07-26 00:20:45 +08:00
if (selectedLayout !== prevProps.selectedLayout) {
Session.setItem('isGridEnabled', selectedLayout === LAYOUT_TYPE.VIDEO_FOCUS);
2023-07-26 00:20:45 +08:00
}
});
return null;
};
2022-09-03 02:18:17 +08:00
const PushLayoutEngineContainer = (props) => {
const cameraDockOutput = layoutSelectOutput((i) => i.cameraDock);
const cameraDockInput = layoutSelectInput((i) => i.cameraDock);
const presentationInput = layoutSelectInput((i) => i.presentation);
const layoutContextDispatch = layoutDispatch();
const applicationSettings = useSettings(SETTINGS.APPLICATION);
const {
selectedLayout,
pushLayout,
} = applicationSettings;
const {
width: cameraWidth,
height: cameraHeight,
position: cameraPosition,
focusedId: focusedCamera,
} = cameraDockOutput;
const {
isResizing: cameraIsResizing,
} = cameraDockInput;
const horizontalPosition = cameraPosition === 'contentLeft' || cameraPosition === 'contentRight';
const {
data: currentMeeting,
} = useMeeting((m) => ({
layout: m.layout,
}));
const meetingLayout = LAYOUT_TYPE[currentMeeting?.layout.currentLayoutType];
const meetingLayoutUpdatedAt = new Date(currentMeeting?.layout.updatedAt).getTime();
const {
propagateLayout: pushLayoutMeeting,
cameraDockIsResizing: isMeetingLayoutResizing,
cameraDockPlacement: meetingLayoutCameraPosition,
cameraDockAspectRatio: meetingLayoutVideoRate,
cameraWithFocus: meetingLayoutFocusedCamera,
presentationMinimized: meetingPresentationMinimized,
} = (currentMeeting?.layout || {});
const { isOpen: presentationIsOpen } = presentationInput;
const { data: currentUserData } = useCurrentUser((user) => ({
enforceLayout: user.enforceLayout,
isModerator: user.isModerator,
presenter: user.presenter,
}));
const isModerator = currentUserData?.isModerator;
const isPresenter = currentUserData?.presenter;
const presentationVideoRate = calculatePresentationVideoRate(cameraDockOutput);
const setLocalSettings = useUserChangedLocalSettings();
const setPushLayout = usePushLayoutUpdater(pushLayout);
const setMeetingLayout = useMeetingLayoutUpdater(
cameraDockOutput,
cameraDockInput,
presentationInput,
applicationSettings,
);
const validateEnforceLayout = (currUser) => {
const layoutTypes = Object.keys(LAYOUT_TYPE);
const enforceLayout = currUser?.enforceLayout;
return enforceLayout && layoutTypes.includes(enforceLayout) ? enforceLayout : null;
};
const enforceLayout = validateEnforceLayout(currentUserData);
const meetingPresentationIsOpen = !meetingPresentationMinimized;
return (
<PushLayoutEngine
{...{
cameraWidth,
cameraHeight,
horizontalPosition,
meetingLayout,
meetingLayoutCameraPosition,
meetingLayoutFocusedCamera,
meetingLayoutVideoRate,
meetingPresentationIsOpen,
enforceLayout,
setLocalSettings,
pushLayoutMeeting,
cameraIsResizing,
cameraPosition,
focusedCamera,
isMeetingLayoutResizing,
isModerator,
isPresenter,
layoutContextDispatch,
meetingLayoutUpdatedAt,
presentationIsOpen,
presentationVideoRate,
pushLayout,
selectedLayout,
setMeetingLayout,
setPushLayout,
2024-09-12 01:43:45 +08:00
hasMeetingLayout: !!meetingLayout,
...props,
}}
/>
);
};
2022-09-03 02:18:17 +08:00
PushLayoutEngine.propTypes = propTypes;
export default PushLayoutEngineContainer;