refactor: joining
This commit is contained in:
@@ -8,8 +8,9 @@ export type VoiceBasicInfo = {
|
||||
|
||||
export type VoicingInfo = {
|
||||
downlinkNetworkQuality?: number,
|
||||
muted: boolean,
|
||||
deafen: boolean
|
||||
muted?: boolean,
|
||||
deafen?: boolean,
|
||||
joining?: boolean
|
||||
} & VoiceBasicInfo
|
||||
|
||||
export type VoicingMemberInfo = {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import AgoraRTC, { IMicrophoneAudioTrack } from 'agora-rtc-sdk-ng';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useGetAgoraConfigQuery, useGetAgoraVoicingListQuery, useLazyGetAgoraTokenQuery } from '../../app/services/server';
|
||||
import { updateChannelVisibleAside } from '../../app/slices/channels';
|
||||
import { addVoiceMember, removeVoiceMember, updateDeafenStatus, updateMuteStatus, updateVoicingInfo, updateVoicingMember, updateVoicingNetworkQuality } from '../../app/slices/voice';
|
||||
import { useAppSelector } from '../../app/store';
|
||||
|
||||
@@ -136,9 +137,14 @@ const useVoice = ({ id, context = "channel" }: VoiceProps) => {
|
||||
};
|
||||
});
|
||||
const [generateToken] = useLazyGetAgoraTokenQuery();
|
||||
const [joining, setJoining] = useState(false);
|
||||
// const [joining, setJoining] = useState(false);
|
||||
const joinVoice = async () => {
|
||||
setJoining(true);
|
||||
// setJoining(true);
|
||||
dispatch(updateVoicingInfo({
|
||||
id,
|
||||
context,
|
||||
joining: true
|
||||
}));
|
||||
const { isError, data } = await generateToken(id);
|
||||
if (!isError && data) {
|
||||
const { channel_name, app_id, agora_token, uid } = data;
|
||||
@@ -153,14 +159,23 @@ const useVoice = ({ id, context = "channel" }: VoiceProps) => {
|
||||
dispatch(updateVoicingInfo({
|
||||
deafen: false,
|
||||
muted: false,
|
||||
joining: false,
|
||||
id,
|
||||
context,
|
||||
}));
|
||||
// 把自己加进去
|
||||
dispatch(addVoiceMember(uid));
|
||||
}
|
||||
} else {
|
||||
console.error("generate agora token error");
|
||||
dispatch(updateVoicingInfo({
|
||||
joining: false,
|
||||
id,
|
||||
context,
|
||||
}));
|
||||
|
||||
}
|
||||
setJoining(false);
|
||||
// setJoining(false);
|
||||
};
|
||||
const leave = async () => {
|
||||
if (window.VOICE_CLIENT && localTrack) {
|
||||
@@ -168,6 +183,11 @@ const useVoice = ({ id, context = "channel" }: VoiceProps) => {
|
||||
localTrack = null;
|
||||
await window.VOICE_CLIENT.leave();
|
||||
dispatch(updateVoicingInfo(null));
|
||||
if (context == "channel") {
|
||||
dispatch(updateChannelVisibleAside({
|
||||
id, aside: null
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
const setMute = (mute: boolean) => {
|
||||
@@ -194,13 +214,15 @@ const useVoice = ({ id, context = "channel" }: VoiceProps) => {
|
||||
dispatch(updateDeafenStatus(deafen));
|
||||
}
|
||||
};
|
||||
const joinedAtThisContext = voicingInfo ? (voicingInfo.id == id && voicingInfo.context == context) : false;
|
||||
return {
|
||||
setMute,
|
||||
setDeafen,
|
||||
leave,
|
||||
// canVoice,
|
||||
voicingInfo,
|
||||
joining,
|
||||
joining: voicingInfo ? voicingInfo.joining : undefined,
|
||||
joinedAtThisContext,
|
||||
joined: !!voicingInfo,
|
||||
joinVoice
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// import { useEffect } from 'react';
|
||||
// import { useDispatch } from 'react-redux';
|
||||
import VoiceManagement from './VoiceManagement';
|
||||
import JoinVoice from './JoinVoice';
|
||||
import { useVoice } from '../../../common/component/Voice';
|
||||
|
||||
type Props = {
|
||||
@@ -11,12 +10,12 @@ type Props = {
|
||||
}
|
||||
|
||||
const Dashboard = ({ context = "channel", id, visible }: Props) => {
|
||||
const { joinVoice, joined, joining, voicingInfo, setMute, setDeafen, leave } = useVoice({ id, context });
|
||||
const { voicingInfo, setMute, setDeafen, leave } = useVoice({ id, context });
|
||||
// const dispatch = useDispatch();
|
||||
|
||||
|
||||
return <div className={`h-full flex-col gap-1 w-[226px] overflow-y-scroll p-2 shadow-[inset_1px_0px_0px_rgba(0,_0,_0,_0.1)] ${visible ? "flex" : "hidden"}`}>
|
||||
{joined ? <VoiceManagement info={voicingInfo} setMute={setMute} setDeafen={setDeafen} leave={leave} /> : <JoinVoice join={joinVoice} joining={joining} />}
|
||||
return <div className={`h-full flex-col gap-1 w-[226px] overflow-y-scroll overflow-x-hidden p-2 shadow-[inset_1px_0px_0px_rgba(0,_0,_0,_0.1)] ${visible ? "flex" : "hidden"}`}>
|
||||
<VoiceManagement info={voicingInfo} setMute={setMute} setDeafen={setDeafen} leave={leave} />
|
||||
</div>;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import React from 'react';
|
||||
import StyledButton from '../../../common/component/styled/Button';
|
||||
|
||||
type Props = {
|
||||
join: () => void,
|
||||
joining: boolean
|
||||
}
|
||||
|
||||
const JoinVoice = ({ joining, join }: Props) => {
|
||||
if (joining) return <div className='w-full h-full flex-center p-1 text-sm text-gray-600 dark:text-gray-400'>
|
||||
Connecting to voice channel...
|
||||
</div>;
|
||||
return (
|
||||
<div className='w-full h-full flex-center p-4'>
|
||||
<StyledButton className='w-full' onClick={join}>Start Audio Chat</StyledButton>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default JoinVoice;
|
||||
@@ -30,15 +30,20 @@ const VoiceManagement = ({ info, setMute, setDeafen, leave }: Props) => {
|
||||
const nameClass = clsx(`text-sm text-gray-500 max-w-[120px] truncate font-semibold dark:text-white`);
|
||||
const members = voicingMembers.ids;
|
||||
const membersData = voicingMembers.byId;
|
||||
if (info.joining) {
|
||||
return <div className='w-full h-full flex-center p-1 text-sm text-gray-600 dark:text-gray-400'>
|
||||
Connecting to voice channel...
|
||||
</div>;
|
||||
}
|
||||
return (
|
||||
<div className='w-full h-full py-2 flex flex-col'>
|
||||
<ul className='flex grow flex-col gap-2'>
|
||||
<ul className='flex grow flex-col'>
|
||||
{members.map((uid) => {
|
||||
const curr = userData[uid];
|
||||
if (!curr) return null;
|
||||
const { muted, deafen, speakingVolume = 0 } = membersData[uid];
|
||||
const { muted, speakingVolume = 0 } = membersData[uid];
|
||||
const speaking = speakingVolume > 50;
|
||||
return <li key={uid} className="flex items-center justify-between gap-6 ">
|
||||
return <li key={uid} className="flex items-center justify-between gap-6 pb-4 ">
|
||||
<div className="flex items-center gap-2 transition-opacity" style={{ opacity: `${speaking ? 0.4 : 1}` }}>
|
||||
<div className="w-8 h-8 flex shrink-0">
|
||||
<Avatar
|
||||
@@ -55,7 +60,7 @@ const VoiceManagement = ({ info, setMute, setDeafen, leave }: Props) => {
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{deafen ? <IconHeadphoneOff className="w-4" /> : <IconHeadphone className="w-4" />}
|
||||
{/* {deafen ? <IconHeadphoneOff className="w-4" /> : <IconHeadphone className="w-4" />} */}
|
||||
{muted ? <IconMicOff className="w-4 fill-gray-500" /> : <IconMic className="w-4 fill-gray-500" />}
|
||||
</div>
|
||||
{/* <User uid={uid} interactive={false} /> */}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { updateChannelVisibleAside } from '../../../app/slices/channels';
|
||||
import { useAppSelector } from '../../../app/store';
|
||||
import IconHeadphone from '../../../assets/icons/headphone.svg';
|
||||
import Tooltip from '../../../common/component/Tooltip';
|
||||
// import Dashboard from './Dashboard';
|
||||
import { useVoice } from '../../../common/component/Voice';
|
||||
|
||||
type Props = {
|
||||
context?: "channel" | "dm"
|
||||
@@ -15,8 +15,15 @@ type Props = {
|
||||
}
|
||||
|
||||
const VoiceChat = ({ id, context = "channel" }: Props) => {
|
||||
const { joinVoice, joined, joining = false, joinedAtThisContext } = useVoice({ id, context });
|
||||
const dispatch = useDispatch();
|
||||
const { loginUser, contextData } = useAppSelector(store => { return { loginUser: store.authData.user, contextData: context == "channel" ? store.channels.byId[id] : store.users.byId[id] }; });
|
||||
const { loginUser, contextData, voiceList } = useAppSelector(store => {
|
||||
return {
|
||||
loginUser: store.authData.user,
|
||||
contextData: context == "channel" ? store.channels.byId[id] : store.users.byId[id],
|
||||
voiceList: store.voice.list
|
||||
};
|
||||
});
|
||||
const { t } = useTranslation("chat");
|
||||
const toggleDashboard = () => {
|
||||
if (context == "channel") {
|
||||
@@ -27,12 +34,30 @@ const VoiceChat = ({ id, context = "channel" }: Props) => {
|
||||
}
|
||||
// todo DM
|
||||
};
|
||||
const handleJoin = () => {
|
||||
if (joining || joined) {
|
||||
alert("You have joined another channel, please leave first!");
|
||||
return;
|
||||
}
|
||||
joinVoice();
|
||||
if (context == "channel") {
|
||||
dispatch(updateChannelVisibleAside({
|
||||
id,
|
||||
aside: "voice"
|
||||
}));
|
||||
}
|
||||
};
|
||||
if (!loginUser) return null;
|
||||
const visible = contextData.visibleAside == "voice";
|
||||
const memberCount = voiceList.find((v) => v.context == context && v.id == id)?.memberCount ?? 0;
|
||||
const badgeClass = `absolute -top-1 -right-1 w-4 h-4 rounded-full bg-primary-400 text-white `;
|
||||
return (
|
||||
<Tooltip disabled={visible} tip={t("voice")} placement="left">
|
||||
<li className={`relative`} >
|
||||
<IconHeadphone className={visible ? "fill-gray-600" : "fill-gray-500"} role="button" onClick={toggleDashboard} />
|
||||
<li className={`relative group`} >
|
||||
<IconHeadphone className={visible ? "fill-gray-600" : "fill-gray-500"} role="button" onClick={joinedAtThisContext ? toggleDashboard : handleJoin} />
|
||||
{visible ? null : memberCount > 0 ? <span className={`${badgeClass} flex-center font-bold text-[10px]`}>{memberCount}</span> : <span className={`${badgeClass} hidden text-xs group-hover:flex-center`}>
|
||||
<em className='font-normal'>+</em>
|
||||
</span>}
|
||||
</li>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user