feat: video call
This commit is contained in:
@@ -8,6 +8,9 @@ import IconMicOff from '../../assets/icons/mic.off.svg';
|
||||
import IconCallOff from '../../assets/icons/headphone.svg';
|
||||
import IconSoundOn from '../../assets/icons/sound.on.svg';
|
||||
import IconSoundOff from '../../assets/icons/sound.off.svg';
|
||||
import IconCameraOff from '../../assets/icons/camera.off.svg';
|
||||
import IconCamera from '../../assets/icons/camera.svg';
|
||||
import IconScreen from '../../assets/icons/share.screen.svg';
|
||||
import { useVoice } from '../../common/component/Voice';
|
||||
import Signal from '../../common/component/Signal';
|
||||
import Tooltip from '../../common/component/Tooltip';
|
||||
@@ -19,7 +22,7 @@ type Props = {
|
||||
|
||||
const RTCWidget = ({ id, context = "channel" }: Props) => {
|
||||
const { t } = useTranslation("chat");
|
||||
const { leave, voicingInfo, setMute, setDeafen, joining = true } = useVoice({ context, id });
|
||||
const { leave, voicingInfo, setMute, setDeafen, joining = true, openCamera } = useVoice({ context, id });
|
||||
const { loginUser, channelData, userData } = useAppSelector(store => {
|
||||
return {
|
||||
userData: store.users.byId,
|
||||
@@ -28,25 +31,36 @@ const RTCWidget = ({ id, context = "channel" }: Props) => {
|
||||
};
|
||||
});
|
||||
if (!loginUser || !voicingInfo || joining) return null;
|
||||
const name = voicingInfo.context == "channel" ? channelData[voicingInfo.id]?.name : userData[voicingInfo.id]?.name;
|
||||
if (!name) return null;
|
||||
// const name = voicingInfo.context == "channel" ? channelData[voicingInfo.id]?.name : userData[voicingInfo.id]?.name;
|
||||
// if (!name) return null;
|
||||
const isReConnecting = voicingInfo.connectionState == "RECONNECTING";
|
||||
return (
|
||||
<div className='bg-gray-100 dark:bg-gray-900 flex flex-col p-2 rounded-3xl m-3 mb-4 text-sm'>
|
||||
{/* {voicingInfo && */}
|
||||
<div className="flex justify-between items-center border-b border-b-gray-200 dark:border-b-gray-800 pb-2">
|
||||
<div className="flex flex-1 items-center gap-1">
|
||||
<Signal strength={voicingInfo.downlinkNetworkQuality} />
|
||||
<div className="flex flex-col">
|
||||
<span className={clsx('text-green-800 font-bold', isReConnecting && `text-red-500`)}>{isReConnecting ? `Voice Reconnecting...` : `Voice Connected`}</span>
|
||||
<span className='text-gray-600 dark:text-gray-400 text-xs truncate max-w-[170px]' >{voicingInfo.context == "channel" ? "Channel" : "DM"} / {voicingInfo.context == "channel" ? channelData[voicingInfo.id].name : userData[voicingInfo.id].name}</span>
|
||||
<div className="border-b border-b-gray-200 dark:border-b-gray-800 pb-2">
|
||||
<div className="flex justify-between items-center">
|
||||
|
||||
<div className="flex flex-1 items-center gap-1">
|
||||
<Signal strength={voicingInfo.downlinkNetworkQuality} />
|
||||
<div className="flex flex-col">
|
||||
<span className={clsx('text-green-800 font-bold', isReConnecting && `text-red-500`)}>{isReConnecting ? `Voice Reconnecting...` : `Voice Connected`}</span>
|
||||
<span className='text-gray-600 dark:text-gray-400 text-xs truncate max-w-[170px]' >{voicingInfo.context == "channel" ? "Channel" : "DM"} / {voicingInfo.context == "channel" ? channelData[voicingInfo.id].name : userData[voicingInfo.id].name}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Tooltip tip={t("leave_voice")} placement="top">
|
||||
<IconCallOff onClick={leave} role="button" className="fill-red-600" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mt-3">
|
||||
<button onClick={openCamera} className='rounded-lg py-2 px-5 bg-white/50 dark:bg-black/50 flex items-center gap-1'>
|
||||
<IconCameraOff className="fill-gray-800 dark:fill-gray-200 w-6 h-6" />
|
||||
<span className='text-sm text-gray-800 dark:text-gray-200 font-semibold'>Video</span>
|
||||
</button>
|
||||
<button className='rounded-lg py-2 px-5 bg-white/50 dark:bg-black/50 flex items-center gap-1'>
|
||||
<IconScreen className="fill-gray-800 dark:fill-gray-200 w-6 h-6" />
|
||||
<span className='text-sm text-gray-800 dark:text-gray-200 font-semibold'>Screen</span>
|
||||
</button>
|
||||
</div>
|
||||
<Tooltip tip={t("leave_voice")} placement="top">
|
||||
<IconCallOff onClick={leave} role="button" className="fill-red-600" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/* } */}
|
||||
<div className="flex justify-between items-center pt-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<User uid={loginUser.uid} compact />
|
||||
@@ -55,7 +69,6 @@ const RTCWidget = ({ id, context = "channel" }: Props) => {
|
||||
<span className='text-gray-400 text-xs'>#{loginUser.uid}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* {voicingInfo && */}
|
||||
<div className="flex gap-2 px-1">
|
||||
<Tooltip tip={voicingInfo.deafen ? t("undeafen") : t("deafen")} placement="top">
|
||||
{voicingInfo.deafen ? <IconSoundOff role="button" onClick={setDeafen.bind(null, false)} /> : <IconSoundOn role="button" onClick={setDeafen.bind(null, true)} />}
|
||||
@@ -67,7 +80,6 @@ const RTCWidget = ({ id, context = "channel" }: Props) => {
|
||||
<IconMic onClick={setMute.bind(null, true)} role="button" />}
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/* } */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -9,6 +9,9 @@ import IconHeadphone from '../../../assets/icons/sound.on.svg';
|
||||
import IconHeadphoneOff from '../../../assets/icons/sound.off.svg';
|
||||
import IconMic from '../../../assets/icons/mic.on.svg';
|
||||
import IconMicOff from '../../../assets/icons/mic.off.svg';
|
||||
import IconCameraOff from '../../../assets/icons/camera.off.svg';
|
||||
import IconCamera from '../../../assets/icons/camera.svg';
|
||||
import IconScreen from '../../../assets/icons/share.screen.svg';
|
||||
import StyledButton from '../../../common/component/styled/Button';
|
||||
import IconCallOff from '../../../assets/icons/call.off.svg';
|
||||
import Tooltip from '../../../common/component/Tooltip';
|
||||
@@ -55,29 +58,32 @@ const VoiceManagement = ({ info, setMute, setDeafen, leave }: Props) => {
|
||||
if (!curr) return null;
|
||||
const { muted, speakingVolume = 0 } = membersData[uid];
|
||||
const speaking = speakingVolume > 50;
|
||||
return <li key={uid} className="flex items-center justify-between gap-6 pb-4 ">
|
||||
<div className="flex items-center gap-2 transition-opacity">
|
||||
<div className={clsx("w-8 h-8 flex shrink-0 relative")}>
|
||||
{speaking && <div className="z-10 absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0 rounded-full bg-green-500 animate-speaking w-[36px] h-[36px]"></div>}
|
||||
<Avatar
|
||||
width={32}
|
||||
height={32}
|
||||
className="w-full h-full rounded-full object-cover z-20"
|
||||
src={curr.avatar}
|
||||
name={curr.name}
|
||||
alt="avatar"
|
||||
/>
|
||||
return <li key={uid} className='pb-4'>
|
||||
<div className="flex items-center justify-between gap-6 pb-2">
|
||||
<div className="flex items-center gap-2 transition-opacity">
|
||||
<div className={clsx("w-8 h-8 flex shrink-0 relative")}>
|
||||
{speaking && <div className="z-10 absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0 rounded-full bg-green-500 animate-speaking w-[36px] h-[36px]"></div>}
|
||||
<Avatar
|
||||
width={32}
|
||||
height={32}
|
||||
className="w-full h-full rounded-full object-cover z-20"
|
||||
src={curr.avatar}
|
||||
name={curr.name}
|
||||
alt="avatar"
|
||||
/>
|
||||
</div>
|
||||
<span className={nameClass} title={curr?.name}>
|
||||
{curr?.name}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* {deafen ? <IconHeadphoneOff className="w-4" /> : <IconHeadphone className="w-4" />} */}
|
||||
{muted ? <IconMicOff className="w-4 fill-gray-500 dark:fill-gray-400" /> : <IconMic className="w-4 fill-gray-500 dark:fill-gray-400" />}
|
||||
</div>
|
||||
<span className={nameClass} title={curr?.name}>
|
||||
{curr?.name}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* {deafen ? <IconHeadphoneOff className="w-4" /> : <IconHeadphone className="w-4" />} */}
|
||||
{muted ? <IconMicOff className="w-4 fill-gray-500 dark:fill-gray-400" /> : <IconMic className="w-4 fill-gray-500 dark:fill-gray-400" />}
|
||||
<div id={`CAMERA_${uid}`} className="w-[98%] h-[120px] rounded overflow-hidden m-auto">
|
||||
{/* camera placeholder */}
|
||||
</div>
|
||||
{/* <User uid={uid} interactive={false} /> */}
|
||||
{/* {userData[uid]?.name} */}
|
||||
</li>;
|
||||
})}
|
||||
|
||||
@@ -95,6 +101,17 @@ const VoiceManagement = ({ info, setMute, setDeafen, leave }: Props) => {
|
||||
|
||||
</li>
|
||||
</Tooltip>
|
||||
<Tooltip tip={"Camera"} placement="top">
|
||||
<li role={"button"} className="py-2 px-3 rounded bg-gray-100 dark:bg-gray-900">
|
||||
{muted ? <IconCameraOff className="fill-gray-700 dark:fill-gray-300" /> : <IconCamera className="fill-gray-700 dark:fill-gray-300" />}
|
||||
|
||||
</li>
|
||||
</Tooltip>
|
||||
<Tooltip tip={"Share Screen"} placement="top">
|
||||
<li role={"button"} onClick={setMute.bind(null, !muted)} className="py-2 px-3 rounded bg-gray-100 dark:bg-gray-900">
|
||||
<IconScreen className="fill-gray-700 dark:fill-gray-300" />
|
||||
</li>
|
||||
</Tooltip>
|
||||
</ul>
|
||||
<StyledButton onClick={leave} className='bg-red-600 hover:!bg-red-700 text-center'>
|
||||
<Tooltip tip={t("leave_voice")} placement="top" offset={[0, 24]} >
|
||||
|
||||
Reference in New Issue
Block a user