2024-04-20 04:34:43 +08:00
|
|
|
import React, { memo, useEffect, useState } from 'react';
|
2024-05-02 03:48:12 +08:00
|
|
|
import { FetchResult } from '@apollo/client';
|
2024-04-20 04:34:43 +08:00
|
|
|
import ButtonEmoji from '/imports/ui/components/common/button/button-emoji/ButtonEmoji';
|
2024-05-02 03:48:12 +08:00
|
|
|
import { IntlShape, defineMessages, injectIntl } from 'react-intl';
|
2024-04-20 04:34:43 +08:00
|
|
|
import deviceInfo from '/imports/utils/deviceInfo';
|
|
|
|
import { debounce } from '/imports/utils/debounce';
|
|
|
|
import BBBMenu from '/imports/ui/components/common/menu/component';
|
|
|
|
import Button from '/imports/ui/components/common/button/component';
|
|
|
|
import VideoPreviewContainer from '/imports/ui/components/video-preview/container';
|
|
|
|
import { CameraSettingsDropdownItemType } from 'bigbluebutton-html-plugin-sdk/dist/cjs/extensible-areas/camera-settings-dropdown-item/enums';
|
2024-05-29 21:26:11 +08:00
|
|
|
import { getSettingsSingletonInstance } from '/imports/ui/services/settings';
|
2024-05-02 03:48:12 +08:00
|
|
|
import { CameraSettingsDropdownInterface } from 'bigbluebutton-html-plugin-sdk';
|
|
|
|
import VideoService from '../service';
|
|
|
|
import Styled from './styles';
|
2024-04-20 04:34:43 +08:00
|
|
|
|
|
|
|
const intlMessages = defineMessages({
|
|
|
|
videoSettings: {
|
|
|
|
id: 'app.video.videoSettings',
|
|
|
|
description: 'Open video settings',
|
|
|
|
},
|
|
|
|
visualEffects: {
|
|
|
|
id: 'app.video.visualEffects',
|
|
|
|
description: 'Visual effects label',
|
|
|
|
},
|
|
|
|
joinVideo: {
|
|
|
|
id: 'app.video.joinVideo',
|
|
|
|
description: 'Join video button label',
|
|
|
|
},
|
|
|
|
leaveVideo: {
|
|
|
|
id: 'app.video.leaveVideo',
|
|
|
|
description: 'Leave video button label',
|
|
|
|
},
|
|
|
|
advancedVideo: {
|
|
|
|
id: 'app.video.advancedVideo',
|
|
|
|
description: 'Open advanced video label',
|
|
|
|
},
|
|
|
|
videoLocked: {
|
|
|
|
id: 'app.video.videoLocked',
|
|
|
|
description: 'video disabled label',
|
|
|
|
},
|
|
|
|
videoConnecting: {
|
|
|
|
id: 'app.video.connecting',
|
|
|
|
description: 'video connecting label',
|
|
|
|
},
|
|
|
|
camCapReached: {
|
|
|
|
id: 'app.video.meetingCamCapReached',
|
|
|
|
description: 'meeting camera cap label',
|
|
|
|
},
|
2024-07-19 20:52:55 +08:00
|
|
|
disconnected: {
|
2024-04-20 04:34:43 +08:00
|
|
|
id: 'app.video.clientDisconnected',
|
2024-07-19 20:52:55 +08:00
|
|
|
description: 'Client disconnected label',
|
2024-04-20 04:34:43 +08:00
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const JOIN_VIDEO_DELAY_MILLISECONDS = 500;
|
|
|
|
|
2024-05-02 03:48:12 +08:00
|
|
|
interface JoinVideoButtonProps {
|
|
|
|
cameraSettingsDropdownItems: CameraSettingsDropdownInterface[];
|
|
|
|
hasVideoStream: boolean;
|
|
|
|
updateSettings: (
|
|
|
|
obj: object,
|
|
|
|
msgDescriptor: object | null,
|
|
|
|
mutation: (settings: Record<string, unknown>) => void,
|
|
|
|
) => void;
|
|
|
|
disableReason: string | undefined;
|
|
|
|
status: string;
|
|
|
|
setLocalSettings: (settings: Record<string, unknown>) => Promise<FetchResult<object>>;
|
|
|
|
exitVideo: () => void;
|
|
|
|
stopVideo: (cameraId?: string | undefined) => void;
|
|
|
|
intl: IntlShape;
|
2024-07-31 02:53:49 +08:00
|
|
|
videoConnecting: boolean; // Added videoConnecting prop
|
2024-05-02 03:48:12 +08:00
|
|
|
}
|
2024-04-20 04:34:43 +08:00
|
|
|
|
2024-05-02 03:48:12 +08:00
|
|
|
const JoinVideoButton: React.FC<JoinVideoButtonProps> = ({
|
2024-04-20 04:34:43 +08:00
|
|
|
intl,
|
|
|
|
hasVideoStream,
|
|
|
|
status,
|
|
|
|
disableReason,
|
|
|
|
updateSettings,
|
|
|
|
cameraSettingsDropdownItems,
|
|
|
|
setLocalSettings,
|
|
|
|
exitVideo: exit,
|
2024-05-02 03:48:12 +08:00
|
|
|
stopVideo,
|
2024-07-31 02:53:49 +08:00
|
|
|
videoConnecting, // Added videoConnecting prop
|
2024-04-20 04:34:43 +08:00
|
|
|
}) => {
|
|
|
|
const { isMobile } = deviceInfo;
|
|
|
|
const isMobileSharingCamera = hasVideoStream && isMobile;
|
|
|
|
const isDesktopSharingCamera = hasVideoStream && !isMobile;
|
2024-05-29 21:26:11 +08:00
|
|
|
|
|
|
|
const ENABLE_WEBCAM_SELECTOR_BUTTON = window.meetingClientSettings.public.app.enableWebcamSelectorButton;
|
|
|
|
|
2024-04-20 04:34:43 +08:00
|
|
|
const shouldEnableWebcamSelectorButton = ENABLE_WEBCAM_SELECTOR_BUTTON
|
|
|
|
&& isDesktopSharingCamera;
|
|
|
|
const exitVideo = () => isDesktopSharingCamera && (!VideoService.isMultipleCamerasEnabled()
|
|
|
|
|| shouldEnableWebcamSelectorButton);
|
|
|
|
|
2024-05-02 03:48:12 +08:00
|
|
|
const [propsToPassModal, setPropsToPassModal] = useState<{ isVisualEffects?: boolean }>({});
|
2024-04-20 04:34:43 +08:00
|
|
|
const [forceOpen, setForceOpen] = useState(false);
|
|
|
|
const [isVideoPreviewModalOpen, setVideoPreviewModalIsOpen] = useState(false);
|
|
|
|
const [wasSelfViewDisabled, setWasSelfViewDisabled] = useState(false);
|
|
|
|
|
|
|
|
useEffect(() => {
|
2024-05-29 21:26:11 +08:00
|
|
|
const Settings = getSettingsSingletonInstance();
|
2024-04-20 04:34:43 +08:00
|
|
|
const isSelfViewDisabled = Settings.application.selfViewDisable;
|
|
|
|
|
|
|
|
if (isVideoPreviewModalOpen && isSelfViewDisabled) {
|
|
|
|
setWasSelfViewDisabled(true);
|
|
|
|
const obj = {
|
|
|
|
application:
|
|
|
|
{ ...Settings.application, selfViewDisable: false },
|
|
|
|
};
|
|
|
|
updateSettings(obj, null, setLocalSettings);
|
|
|
|
}
|
|
|
|
}, [isVideoPreviewModalOpen]);
|
|
|
|
|
|
|
|
const handleOnClick = debounce(() => {
|
|
|
|
switch (status) {
|
|
|
|
case 'videoConnecting':
|
2024-05-02 03:48:12 +08:00
|
|
|
stopVideo();
|
2024-04-20 04:34:43 +08:00
|
|
|
break;
|
|
|
|
case 'connected':
|
|
|
|
default:
|
|
|
|
if (exitVideo()) {
|
|
|
|
exit();
|
|
|
|
} else {
|
|
|
|
setForceOpen(isMobileSharingCamera);
|
|
|
|
setVideoPreviewModalIsOpen(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, JOIN_VIDEO_DELAY_MILLISECONDS);
|
|
|
|
|
2024-05-02 03:48:12 +08:00
|
|
|
const handleOpenAdvancedOptions = (callback?: () => void) => {
|
2024-04-20 04:34:43 +08:00
|
|
|
if (callback) callback();
|
|
|
|
setForceOpen(isDesktopSharingCamera);
|
|
|
|
setVideoPreviewModalIsOpen(true);
|
|
|
|
};
|
|
|
|
|
|
|
|
const getMessageFromStatus = () => {
|
|
|
|
let statusMessage = status;
|
|
|
|
if (status !== 'videoConnecting') {
|
|
|
|
statusMessage = exitVideo() ? 'leaveVideo' : 'joinVideo';
|
|
|
|
}
|
|
|
|
return statusMessage;
|
|
|
|
};
|
|
|
|
|
|
|
|
const label = disableReason
|
2024-05-02 03:48:12 +08:00
|
|
|
? intl.formatMessage(intlMessages[disableReason as keyof typeof intlMessages])
|
|
|
|
: intl.formatMessage(intlMessages[getMessageFromStatus() as keyof typeof intlMessages]);
|
2024-04-20 04:34:43 +08:00
|
|
|
|
|
|
|
const renderUserActions = () => {
|
|
|
|
const actions = [];
|
|
|
|
|
|
|
|
if (shouldEnableWebcamSelectorButton) {
|
|
|
|
actions.push(
|
|
|
|
{
|
|
|
|
key: 'advancedVideo',
|
|
|
|
label: intl.formatMessage(intlMessages.advancedVideo),
|
|
|
|
onClick: () => handleOpenAdvancedOptions(),
|
|
|
|
dataTest: 'advancedVideoSettingsButton',
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (actions.length === 0) return null;
|
|
|
|
const customStyles = { top: '-3.6rem' };
|
|
|
|
|
|
|
|
cameraSettingsDropdownItems.forEach((plugin) => {
|
|
|
|
switch (plugin.type) {
|
|
|
|
case CameraSettingsDropdownItemType.OPTION:
|
|
|
|
actions.push({
|
|
|
|
key: plugin.id,
|
2024-05-02 03:48:12 +08:00
|
|
|
// @ts-expect-error -> Plugin-related.
|
2024-04-20 04:34:43 +08:00
|
|
|
label: plugin.label,
|
2024-05-02 03:48:12 +08:00
|
|
|
// @ts-expect-error -> Plugin-related.
|
2024-04-20 04:34:43 +08:00
|
|
|
onClick: plugin.onClick,
|
2024-05-02 03:48:12 +08:00
|
|
|
// @ts-expect-error -> Plugin-related.
|
2024-04-20 04:34:43 +08:00
|
|
|
icon: plugin.icon,
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
case CameraSettingsDropdownItemType.SEPARATOR:
|
|
|
|
actions.push({
|
|
|
|
key: plugin.id,
|
|
|
|
isSeparator: true,
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return (
|
|
|
|
<BBBMenu
|
|
|
|
customStyles={!isMobile ? customStyles : null}
|
|
|
|
trigger={(
|
|
|
|
<ButtonEmoji
|
|
|
|
emoji="device_list_selector"
|
|
|
|
data-test="videoDropdownMenu"
|
|
|
|
hideLabel
|
|
|
|
label={intl.formatMessage(intlMessages.videoSettings)}
|
|
|
|
rotate
|
|
|
|
tabIndex={0}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
actions={actions}
|
|
|
|
opts={{
|
|
|
|
id: 'video-dropdown-menu',
|
|
|
|
keepMounted: true,
|
|
|
|
transitionDuration: 0,
|
|
|
|
elevation: 3,
|
|
|
|
getcontentanchorel: null,
|
|
|
|
fullwidth: 'true',
|
|
|
|
anchorOrigin: { vertical: 'top', horizontal: 'center' },
|
|
|
|
transformOrigin: { vertical: 'top', horizontal: 'center' },
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<Styled.OffsetBottom>
|
2024-08-06 01:05:04 +08:00
|
|
|
<Button
|
|
|
|
label={label}
|
|
|
|
data-test={hasVideoStream ? 'leaveVideo' : 'joinVideo'}
|
|
|
|
onClick={handleOnClick}
|
|
|
|
hideLabel
|
|
|
|
color={hasVideoStream ? 'primary' : 'default'}
|
|
|
|
icon={hasVideoStream ? 'video' : 'video_off'}
|
|
|
|
ghost={!hasVideoStream}
|
|
|
|
size="lg"
|
|
|
|
circle
|
|
|
|
disabled={!!disableReason}
|
|
|
|
loading={videoConnecting}
|
|
|
|
/>
|
2024-04-20 04:34:43 +08:00
|
|
|
{renderUserActions()}
|
|
|
|
</Styled.OffsetBottom>
|
|
|
|
{isVideoPreviewModalOpen ? (
|
|
|
|
<VideoPreviewContainer
|
|
|
|
{...{
|
|
|
|
callbackToClose: () => {
|
|
|
|
if (wasSelfViewDisabled) {
|
|
|
|
setTimeout(() => {
|
|
|
|
const obj = {
|
2024-05-02 03:48:12 +08:00
|
|
|
application: {
|
2024-08-06 01:19:34 +08:00
|
|
|
// @ts-expect-error -> Untyped object.
|
2024-05-02 03:48:12 +08:00
|
|
|
...Settings.application,
|
|
|
|
selfViewDisable: true,
|
|
|
|
},
|
2024-04-20 04:34:43 +08:00
|
|
|
};
|
|
|
|
updateSettings(obj, null, setLocalSettings);
|
|
|
|
setWasSelfViewDisabled(false);
|
|
|
|
}, 100);
|
|
|
|
}
|
|
|
|
setPropsToPassModal({});
|
|
|
|
setForceOpen(false);
|
|
|
|
},
|
|
|
|
forceOpen,
|
|
|
|
priority: 'low',
|
|
|
|
setIsOpen: setVideoPreviewModalIsOpen,
|
|
|
|
isOpen: isVideoPreviewModalOpen,
|
|
|
|
}}
|
2024-05-02 03:48:12 +08:00
|
|
|
isVisualEffects={propsToPassModal.isVisualEffects}
|
2024-04-20 04:34:43 +08:00
|
|
|
/>
|
|
|
|
) : null}
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default injectIntl(memo(JoinVideoButton));
|