diff --git a/src/app/services/server.ts b/src/app/services/server.ts index 3f126d0f..a5a7329c 100644 --- a/src/app/services/server.ts +++ b/src/app/services/server.ts @@ -22,7 +22,7 @@ import { } from "../../types/server"; import { Channel } from "../../types/channel"; import { ContentTypeKey } from "../../types/message"; -import { updateVoiceInfo } from "../slices/voice"; +import { updateVoiceList } from "../slices/voice"; const defaultExpireDuration = 2 * 24 * 60 * 60; @@ -128,14 +128,17 @@ export const serverApi = createApi({ try { const { data: resp } = await queryFulfilled; const { success } = resp; - if (success && resp.data.channels.length > 0) { - const [channel] = resp.data.channels; - const [id] = channel.channel_name.split(":").slice(-1); - // todo - dispatch(updateVoiceInfo({ - id: +id, - context: "channel" - })); + if (success) { + const arr = resp.data.channels.map(data => { + const [id] = data.channel_name.split(":").slice(-1); + const count = data.user_count; + return { + id: +id, + context: "channel" as const, + memberCount: count + }; + }); + dispatch(updateVoiceList(arr)); } } catch { console.error("get voice list error"); diff --git a/src/app/slices/voice.ts b/src/app/slices/voice.ts index 4205ac62..b0acf632 100644 --- a/src/app/slices/voice.ts +++ b/src/app/slices/voice.ts @@ -1,22 +1,27 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; -export type VoiceInfo = { +export type VoiceBasicInfo = { context: "channel" | "dm" - id: number | null, + id: number, +} +export type VoicingInfo = { members: number[] -} +} & VoiceBasicInfo +export type VoiceInfo = { + memberCount: number +} & VoiceBasicInfo interface State { - joined: boolean, - voicing: VoiceInfo + voicing: VoicingInfo | null, + list: VoiceInfo[] } -const initialInfo = { - context: "channel" as const, - id: null, - members: [] -}; +// const initialInfo = { +// context: "channel" as const, +// id: 0, +// members: [] +// }; const initialState: State = { - joined: false, - voicing: initialInfo + voicing: null, + list: [] }; @@ -25,21 +30,11 @@ const voiceSlice = createSlice({ name: "voice", initialState, reducers: { - updateJoinStatus(state, { payload }: PayloadAction) { - state.joined = payload; + updateVoicingInfo(state, { payload }: PayloadAction) { + state.voicing = payload; }, - updateVoiceInfo(state, { payload }: PayloadAction) { - if (!payload) { - // reset - state.voicing = initialInfo; - } else { - if (state.voicing) { - state.voicing = { ...state.voicing, ...payload }; - } else { - state.voicing = payload; - } - - } + updateVoiceList(state, { payload }: PayloadAction) { + state.list = payload; }, addVoiceMember(state, { payload }: PayloadAction) { if (state.voicing) { @@ -59,5 +54,5 @@ const voiceSlice = createSlice({ }, }); -export const { updateJoinStatus, updateVoiceInfo, addVoiceMember, removeVoiceMember } = voiceSlice.actions; +export const { addVoiceMember, removeVoiceMember, updateVoiceList, updateVoicingInfo } = voiceSlice.actions; export default voiceSlice.reducer; diff --git a/src/common/component/Voice.tsx b/src/common/component/Voice.tsx index ae8a9f2d..ead576f6 100644 --- a/src/common/component/Voice.tsx +++ b/src/common/component/Voice.tsx @@ -1,8 +1,8 @@ -import AgoraRTC from 'agora-rtc-sdk-ng'; +import AgoraRTC, { IMicrophoneAudioTrack } from 'agora-rtc-sdk-ng'; import { useEffect, useState } from 'react'; import { useDispatch } from 'react-redux'; import { useGetAgoraConfigQuery, useGetAgoraVoicingListQuery, useLazyGetAgoraTokenQuery } from '../../app/services/server'; -import { addVoiceMember, removeVoiceMember, updateJoinStatus, updateVoiceInfo } from '../../app/slices/voice'; +import { addVoiceMember, removeVoiceMember, updateVoicingInfo } from '../../app/slices/voice'; import { useAppSelector } from '../../app/store'; // type Props = {} @@ -34,6 +34,7 @@ const Voice = () => { dispatch(addVoiceMember(+user.uid)); // console.log("subscribe success"); // Subscribe and play the remote audio track. + console.log(user.uid + "has joined the channel"); if (mediaType == "audio") { // Play the remote audio track. user.audioTrack?.play(); @@ -71,10 +72,11 @@ type VoiceProps = { } const useVoice = ({ id, context = "channel" }: VoiceProps) => { const dispatch = useDispatch(); - const { voiceInfo, joined } = useAppSelector(store => { + const { voiceInfo, joined, loginUid = 0 } = useAppSelector(store => { return { + loginUid: store.authData.user?.uid, voiceInfo: store.voice.voicing, - joined: store.voice.joined + joined: !!store.voice.voicing }; }); const [generateToken] = useLazyGetAgoraTokenQuery(); @@ -88,25 +90,27 @@ const useVoice = ({ id, context = "channel" }: VoiceProps) => { await window.VOICE_CLIENT.join(app_id, channel_name, agora_token, uid); console.table(data); // Create a local audio track from the microphone audio. - const localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack(); + + window.VOICE_TRACK_MAP[loginUid] = await AgoraRTC.createMicrophoneAudioTrack(); // Publish the local audio track in the channel. - await window.VOICE_CLIENT.publish(localAudioTrack); + await window.VOICE_CLIENT.publish(window.VOICE_TRACK_MAP[loginUid]); console.log("Publish success!"); - dispatch(updateVoiceInfo({ + dispatch(updateVoicingInfo({ id, context, members: [uid] })); - dispatch(updateJoinStatus(true)); } } setJoining(false); }; const leave = async () => { if (window.VOICE_CLIENT) { + (window.VOICE_TRACK_MAP[loginUid] as IMicrophoneAudioTrack).close(); await window.VOICE_CLIENT.leave(); - dispatch(updateJoinStatus(false)); - // window.VOICE_TRACK_MAP + dispatch(updateVoicingInfo(null)); + // window.VOICE_TRACK_MAP[loginUid]?.stop(); + // window.VOICE_TRACK_MAP[loginUid] = null; } }; return { diff --git a/src/common/component/styled/Input.tsx b/src/common/component/styled/Input.tsx index f6a1a5d0..003e03c9 100644 --- a/src/common/component/styled/Input.tsx +++ b/src/common/component/styled/Input.tsx @@ -20,6 +20,7 @@ interface Props | "pattern" | "disabled" | "minLength" + | "spellCheck" >, HTMLInputElement > { diff --git a/src/routes/chat/RTCWidget.tsx b/src/routes/chat/RTCWidget.tsx index c100769a..591d1fd0 100644 --- a/src/routes/chat/RTCWidget.tsx +++ b/src/routes/chat/RTCWidget.tsx @@ -14,17 +14,24 @@ type Props = { const RTCWidget = ({ id, context = "channel" }: Props) => { const { leave } = useVoice({ context, id }); - const { loginUser, joined } = useAppSelector(store => { return { loginUser: store.authData.user, joined: store.voice.joined }; }); + const { loginUser, voicingInfo, channelData, userData } = useAppSelector(store => { + return { + userData: store.users.byId, + channelData: store.channels.byId, + loginUser: store.authData.user, + voicingInfo: store.voice.voicing + }; + }); if (!loginUser) return null; return (
- {joined && + {voicingInfo &&
Voice Connected - Channel / name + {voicingInfo.context == "channel" ? "Channel" : "DM"} / {voicingInfo.context == "channel" ? channelData[voicingInfo.id].name : userData[voicingInfo.id].name}
diff --git a/src/routes/chat/SessionList/Session.tsx b/src/routes/chat/SessionList/Session.tsx index a3d00951..d229241d 100644 --- a/src/routes/chat/SessionList/Session.tsx +++ b/src/routes/chat/SessionList/Session.tsx @@ -16,20 +16,20 @@ import { useAppSelector } from "../../../app/store"; import { fromNowTime } from "../../../common/utils"; interface IProps { - type?: "user" | "channel"; + type?: "dm" | "channel"; id: number; mid: number; setDeleteChannelId: (param: number) => void; setInviteChannelId: (param: number) => void; } const Session: FC = ({ - type = "user", + type = "dm", id, mid, setDeleteChannelId, setInviteChannelId }) => { - const navPath = type == "user" ? `/chat/dm/${id}` : `/chat/channel/${id}`; + const navPath = type == "dm" ? `/chat/dm/${id}` : `/chat/channel/${id}`; // const { pathname } = useLocation(); const isCurrentPath = useMatch(navPath); const navigate = useNavigate(); @@ -46,7 +46,7 @@ const Session: FC = ({ return { size, type, name, url }; }); addStageFile(filesData); - navigate(type == "user" ? `/chat/dm/${id}` : `/chat/channel/${id}`); + navigate(type == "dm" ? `/chat/dm/${id}` : `/chat/channel/${id}`); } }, collect: (monitor) => ({ @@ -62,24 +62,24 @@ const Session: FC = ({ mid: number; is_public: boolean; }>(); - const { messageData, userData, channelData, readIndex, loginUid, mids, muted, voiceInfo } = useAppSelector( + const { messageData, userData, channelData, readIndex, loginUid, mids, muted, voiceList } = useAppSelector( (store) => { return { - voiceInfo: store.voice.voicing, - mids: type == "user" ? store.userMessage.byId[id] : store.channelMessage[id], + voiceList: store.voice.list, + mids: type == "dm" ? store.userMessage.byId[id] : store.channelMessage[id], loginUid: store.authData.user?.uid || 0, readIndex: - type == "user" ? store.footprint.readUsers[id] : store.footprint.readChannels[id], + type == "dm" ? store.footprint.readUsers[id] : store.footprint.readChannels[id], messageData: store.message, userData: store.users.byId, channelData: store.channels.byId, - muted: type == "user" ? store.footprint.muteUsers[id] : store.footprint.muteChannels[id] + muted: type == "dm" ? store.footprint.muteUsers[id] : store.footprint.muteChannels[id] }; } ); useEffect(() => { - const tmp = type == "user" ? userData[id] : channelData[id]; + const tmp = type == "dm" ? userData[id] : channelData[id]; if (!tmp) return; if ("avatar" in tmp) { // user @@ -100,6 +100,7 @@ const Session: FC = ({ messageData, loginUid }); + const isVoicing = type == "channel" && voiceList.findIndex(item => item.id == id) > -1; return (
  • = ({ onContextMenu={handleContextMenuEvent} >
    - {type == "user" ? ( + {type == "dm" ? ( ) : ( = ({ src={icon} /> )} - {type == "channel" && voiceInfo.id == id && } + {isVoicing && }
    diff --git a/src/routes/chat/SessionList/index.tsx b/src/routes/chat/SessionList/index.tsx index bfc3d061..193b95a2 100644 --- a/src/routes/chat/SessionList/index.tsx +++ b/src/routes/chat/SessionList/index.tsx @@ -6,7 +6,7 @@ import DeleteChannelConfirmModal from "../../settingChannel/DeleteConfirmModal"; import InviteModal from "../../../common/component/InviteModal"; import { useAppSelector } from "../../../app/store"; export interface ChatSession { - type: "user" | "channel"; + type: "dm" | "channel"; id: number; mid: number; unread: number; @@ -46,11 +46,11 @@ const SessionList: FC = ({ tempSession }) => { // console.log("adddd", id); const mids = userMessage[id]; if (!mids || mids.length == 0) { - return { unreads: 0, id, type: "user" }; + return { unreads: 0, id, type: "dm" }; } // 先转换成数字,再排序 const mid = [...mids].sort((a, b) => +a - +b).pop(); - return { type: "user", id, mid }; + return { type: "dm", id, mid }; }); const temps = [...(cSessions as ChatSession[]), ...(uSessions as ChatSession[])].sort((a, b) => { const { mid: aMid = 0 } = a; diff --git a/src/routes/chat/index.tsx b/src/routes/chat/index.tsx index e2db40f6..c615cbce 100644 --- a/src/routes/chat/index.tsx +++ b/src/routes/chat/index.tsx @@ -44,7 +44,7 @@ function ChatPage() { mid: 0, unread: 0, id: +user_id, - type: "user" as const + type: "dm" as const } : undefined; // console.log("temp uid", tmpUid); diff --git a/src/routes/setting/config/Agora.tsx b/src/routes/setting/config/Agora.tsx index c2527b0d..c735f5c1 100644 --- a/src/routes/setting/config/Agora.tsx +++ b/src/routes/setting/config/Agora.tsx @@ -54,8 +54,8 @@ export default function ConfigAgora() {