blob: 16daab1d9d7605776658be4049f0f2eea9122b86 [file] [log] [blame]
/*
* Copyright (C) 2022 Savoir-faire Linux Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
import { IconButtonProps } from '@mui/material';
import { styled } from '@mui/material/styles';
import { ChangeEvent, useMemo } from 'react';
import { useCallManagerContext } from '../contexts/CallManagerProvider';
import { useUserMediaContext } from '../contexts/UserMediaProvider';
import { CallStatus, VideoStatus } from '../services/CallManager';
import {
ColoredRoundButton,
ExpandableButton,
ExpandableButtonProps,
ExpandMenuRadioOption,
ToggleIconButton,
} from './Button';
import {
CallEndIcon,
ChatBubbleIcon,
ExtensionIcon,
FullScreenIcon,
GroupAddIcon,
MicroIcon,
MicroOffIcon,
MoreVerticalIcon,
PlaceAudioCallIcon,
RecordingIcon,
RoundCloseIcon,
ScreenShareArrowIcon,
ScreenShareStopIcon,
VideoCameraIcon,
VideoCameraOffIcon,
VolumeIcon,
} from './SvgIcon';
const CallButton = styled((props: ExpandableButtonProps) => {
return <ExpandableButton {...props} />;
})({
color: 'white',
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.15)',
},
});
export const CallingChatButton = (props: ExpandableButtonProps) => {
const { setIsChatShown } = useCallManagerContext();
return (
<CallButton
aria-label="chat"
Icon={ChatBubbleIcon}
onClick={() => {
setIsChatShown((v) => !v);
}}
{...props}
/>
);
};
export const CallingEndButton = (props: ExpandableButtonProps) => {
const { endCall } = useCallManagerContext();
return (
<ColoredRoundButton
paletteColor={(theme) => theme.palette.error}
onClick={() => {
endCall();
}}
aria-label="call end"
Icon={CallEndIcon}
{...props}
/>
);
};
export const CallingExtensionButton = (props: ExpandableButtonProps) => {
return <CallButton aria-label="extensions" Icon={ExtensionIcon} {...props} />;
};
export const CallingFullScreenButton = (props: ExpandableButtonProps) => {
const { setIsFullscreen } = useCallManagerContext();
return (
<CallButton
aria-label="full screen"
Icon={FullScreenIcon}
onClick={() => {
setIsFullscreen((v) => !v);
}}
{...props}
/>
);
};
export const CallingGroupButton = (props: ExpandableButtonProps) => {
return <CallButton aria-label="group options" Icon={GroupAddIcon} {...props} />;
};
export const CallingMoreVerticalButton = (props: ExpandableButtonProps) => {
return <CallButton aria-label="more vertical" Icon={MoreVerticalIcon} {...props} />;
};
export const CallingRecordButton = (props: ExpandableButtonProps) => {
return <CallButton aria-label="recording options" Icon={RecordingIcon} {...props} />;
};
export const CallingScreenShareButton = (props: ExpandableButtonProps) => {
return (
<CallButton aria-label="screen share" expandMenuOnClick IconButtonComp={ToggleScreenShareIconButton} {...props} />
);
};
const ToggleScreenShareIconButton = (props: IconButtonProps) => {
const { videoStatus, updateVideoStatus } = useCallManagerContext();
return (
<ToggleIconButton
IconOff={ScreenShareArrowIcon}
IconOn={ScreenShareStopIcon}
selected={videoStatus === VideoStatus.ScreenShare}
toggle={() => {
updateVideoStatus((v) => (v !== VideoStatus.ScreenShare ? VideoStatus.ScreenShare : VideoStatus.Off));
}}
{...props}
/>
);
};
const useMediaDeviceExpandMenuOptions = (kind: MediaDeviceKind): ExpandMenuRadioOption[] | undefined => {
const { currentMediaDeviceIds, mediaDevices } = useUserMediaContext();
const options = useMemo(
() =>
mediaDevices[kind].map((device) => ({
key: device.deviceId,
description: device.label,
})),
[mediaDevices, kind]
);
const currentDevice = currentMediaDeviceIds[kind];
if (options.length === 0) {
return undefined;
}
return [
{
options,
value: currentDevice.id ?? '',
onChange: (e: ChangeEvent<HTMLInputElement>) => {
currentDevice.setId(e.target.value);
},
},
];
};
export const CallingVolumeButton = (props: ExpandableButtonProps) => {
const options = useMediaDeviceExpandMenuOptions('audiooutput');
return (
<CallButton
aria-label="volume options"
expandMenuOnClick
Icon={VolumeIcon}
expandMenuOptions={options}
{...props}
/>
);
};
export const CallingMicButton = (props: ExpandableButtonProps) => {
const options = useMediaDeviceExpandMenuOptions('audioinput');
return (
<CallButton
aria-label="microphone options"
expandMenuOptions={options}
IconButtonComp={ToggleAudioCameraIconButton}
{...props}
/>
);
};
const ToggleAudioCameraIconButton = (props: IconButtonProps) => {
const { isAudioOn, setIsAudioOn } = useCallManagerContext();
return (
<ToggleIconButton
IconOn={MicroIcon}
IconOff={MicroOffIcon}
selected={isAudioOn}
toggle={() => setIsAudioOn((v) => !v)}
{...props}
/>
);
};
export const CallingVideoCameraButton = (props: ExpandableButtonProps) => {
const options = useMediaDeviceExpandMenuOptions('videoinput');
return (
<CallButton
aria-label="camera options"
expandMenuOptions={options}
IconButtonComp={ToggleVideoCameraIconButton}
{...props}
/>
);
};
const ToggleVideoCameraIconButton = (props: IconButtonProps) => {
const { videoStatus, updateVideoStatus } = useCallManagerContext();
return (
<ToggleIconButton
IconOn={VideoCameraIcon}
IconOff={VideoCameraOffIcon}
selected={videoStatus === VideoStatus.Camera}
toggle={() => {
updateVideoStatus((v) => (v !== VideoStatus.Camera ? VideoStatus.Camera : VideoStatus.Off));
}}
{...props}
/>
);
};
// Calling pending/receiving interface
export const CallingCancelButton = (props: IconButtonProps) => {
const { endCall } = useCallManagerContext();
return (
<ColoredRoundButton
aria-label="cancel call"
onClick={() => {
endCall();
}}
Icon={CallEndIcon}
paletteColor={(theme) => theme.palette.error}
{...props}
/>
);
};
export const CallingAnswerAudioButton = (props: IconButtonProps) => {
const { acceptCall, callStatus } = useCallManagerContext();
return (
<ColoredRoundButton
disabled={callStatus === CallStatus.Loading || callStatus === CallStatus.Connecting}
aria-label="answer call audio"
onClick={() => {
acceptCall(false);
}}
Icon={PlaceAudioCallIcon}
paletteColor={(theme) => theme.palette.success}
{...props}
/>
);
};
export const CallingAnswerVideoButton = (props: IconButtonProps) => {
const { acceptCall, callStatus } = useCallManagerContext();
return (
<ColoredRoundButton
disabled={callStatus === CallStatus.Connecting || callStatus === CallStatus.Loading}
aria-label="answer call video"
onClick={() => {
acceptCall(true);
}}
paletteColor={(theme) => theme.palette.success}
Icon={VideoCameraIcon}
{...props}
/>
);
};
export const CallingRefuseButton = (props: IconButtonProps) => {
const { endCall } = useCallManagerContext();
return (
<ColoredRoundButton
aria-label="refuse call"
onClick={() => {
endCall();
}}
paletteColor={(theme) => theme.palette.error}
Icon={RoundCloseIcon}
{...props}
/>
);
};