diff --git a/src/app/slices/voice.ts b/src/app/slices/voice.ts
index f5acd23c..5d2d0b6a 100644
--- a/src/app/slices/voice.ts
+++ b/src/app/slices/voice.ts
@@ -30,7 +30,8 @@ export type VoicingMembers = {
ids: number[],
byId: {
[key: number]: VoicingMemberInfo
- }
+ },
+ pin?: number
}
export type VoiceInfo = {
memberCount: number
@@ -142,6 +143,12 @@ const voiceSlice = createSlice({
const { uid, info } = payload;
state.voicingMembers.byId[uid] = { ...state.voicingMembers.byId[uid], ...info };
}
+ },
+ updatePin(state, { payload }: PayloadAction<{ uid: number, action: "pin" | "unpin" }>) {
+ const idx = state.voicingMembers.ids.findIndex((uid) => uid == payload.uid);
+ if (idx > -1) {
+ state.voicingMembers.pin = payload.action == "pin" ? payload.uid : undefined;
+ }
}
},
extraReducers: (builder) => {
@@ -149,6 +156,14 @@ const voiceSlice = createSlice({
// reset voicing info
if (window.VOICE_CLIENT) {
window.VOICE_CLIENT.leave();
+ Object.entries(window.VOICE_TRACK_MAP).forEach(([uid, track]) => {
+ track?.close();
+ delete window.VOICE_TRACK_MAP[+uid];
+ });
+ Object.entries(window.VIDEO_TRACK_MAP).forEach(([uid, track]) => {
+ track?.close();
+ delete window.VOICE_TRACK_MAP[+uid];
+ });
}
state.voicing = null;
state.voicingMembers = {
@@ -159,5 +174,5 @@ const voiceSlice = createSlice({
}
});
-export const { updateConnectionState, addVoiceMember, removeVoiceMember, upsertVoiceList, updateVoicingInfo, updateVoicingNetworkQuality, updateMuteStatus, updateVoicingMember, updateDeafenStatus } = voiceSlice.actions;
+export const { updatePin, updateConnectionState, addVoiceMember, removeVoiceMember, upsertVoiceList, updateVoicingInfo, updateVoicingNetworkQuality, updateMuteStatus, updateVoicingMember, updateDeafenStatus } = voiceSlice.actions;
export default voiceSlice.reducer;
diff --git a/src/routes/chat/VoiceChat/Dashboard.tsx b/src/routes/chat/VoiceChat/Dashboard.tsx
index cf7efaaa..2e45c0b8 100644
--- a/src/routes/chat/VoiceChat/Dashboard.tsx
+++ b/src/routes/chat/VoiceChat/Dashboard.tsx
@@ -12,9 +12,6 @@ type Props = {
const Dashboard = ({ context = "channel", id, visible }: Props) => {
const { voicingInfo, setMute, leave, closeCamera, openCamera, startShareScreen, stopShareScreen } = useVoice({ id, context });
- // const dispatch = useDispatch();
-
-
return
;
diff --git a/src/routes/chat/VoiceChat/VoiceManagement.tsx b/src/routes/chat/VoiceChat/VoiceManagement.tsx
index 2c0a1252..0f781d3e 100644
--- a/src/routes/chat/VoiceChat/VoiceManagement.tsx
+++ b/src/routes/chat/VoiceChat/VoiceManagement.tsx
@@ -2,7 +2,7 @@ import clsx from 'clsx';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
-import { VoicingInfo } from '../../../app/slices/voice';
+import { VoicingInfo, updatePin } from '../../../app/slices/voice';
import { useAppSelector } from '../../../app/store';
import Avatar from '../../../common/component/Avatar';
// import IconHeadphone from '../../../assets/icons/sound.on.svg';
@@ -49,9 +49,12 @@ const VoiceManagement = ({ id, context, info, setMute, leave, closeCamera, openC
playAgoraVideo(id);
});
}, [voicingMembers.ids]);
- const handleFullscreen = () => {
+ const handleFullscreen = (uid?: number) => {
if (context == "channel") {
dispatch(updateChannelVisibleAside({ id, aside: "voice_fullscreen" }));
+ if (uid) {
+ dispatch(updatePin({ uid, action: "pin" }));
+ }
}
};
if (!info) return null;
@@ -103,7 +106,7 @@ const VoiceManagement = ({ id, context, info, setMute, leave, closeCamera, openC
{muted ? : }
-
+
{/* camera placeholder */}
;
@@ -128,7 +131,7 @@ const VoiceManagement = ({ id, context, info, setMute, leave, closeCamera, openC
-
+
diff --git a/src/routes/chat/VoiceFullscreen.tsx b/src/routes/chat/VoiceFullscreen.tsx
index 58d22437..ef49d80c 100644
--- a/src/routes/chat/VoiceFullscreen.tsx
+++ b/src/routes/chat/VoiceFullscreen.tsx
@@ -18,6 +18,7 @@ import { updateChannelVisibleAside } from '../../app/slices/footprint';
import Tooltip from '../../common/component/Tooltip';
import { useVoice } from '../../common/component/Voice';
import StyledButton from '../../common/component/styled/Button';
+import { updatePin } from '../../app/slices/voice';
type Props = {
context: ChatContext,
@@ -25,9 +26,8 @@ type Props = {
}
const VoiceFullscreen = ({ id, context }: Props) => {
- const [speakingIndex, setSpeakingIndex] = useState(0);
- const [pin, setPin] = useState
(undefined);
const dispatch = useDispatch();
+ const [speakingUid, setSpeakingUid] = useState(0);
const { t } = useTranslation("chat");
const { voicingInfo, setMute, leave, closeCamera, openCamera, startShareScreen, stopShareScreen } = useVoice({ id, context });
const { name, userData, voicingMembers } = useAppSelector(store => {
@@ -39,12 +39,12 @@ const VoiceFullscreen = ({ id, context }: Props) => {
});
useEffect(() => {
const ids = voicingMembers.ids;
- ids.forEach((id, idx) => {
+ ids.forEach((id) => {
playAgoraVideo(id);
const { speakingVolume = 0 } = voicingMembers.byId[id];
const speaking = speakingVolume > 50;
if (speaking) {
- setSpeakingIndex(idx);
+ setSpeakingUid(id);
}
});
@@ -54,19 +54,29 @@ const VoiceFullscreen = ({ id, context }: Props) => {
dispatch(updateChannelVisibleAside({ id, aside: "voice" }));
}
};
+
+ const handleDoubleClick = (evt: MouseEvent) => {
+ const uid = evt.currentTarget.dataset.uid;
+ if (uid) {
+ dispatch(updatePin({ uid: +uid, action: "pin" }));
+ }
+ };
+
const handlePin = (evt: MouseEvent) => {
- const idx = evt.currentTarget.dataset.idx ?? "";
- if (idx == "undefined") {
- setPin(undefined);
+ const uid = evt.currentTarget.dataset.uid ?? "";
+ const action = evt.currentTarget.dataset.action ?? "pin";
+ if (action == "unpin") {
+ dispatch(updatePin({ uid: +uid, action }));
} else {
- setPin(parseInt(idx));
+ dispatch(updatePin({ uid: +uid, action: "pin" }));
}
};
if (!voicingInfo) return null;
+ const pinUid = voicingMembers.pin;
const _name = context == "channel" ? `# ${name}` : `@ ${name}`;
const members = voicingMembers.ids;
const membersData = voicingMembers.byId;
- const hasPin = typeof pin !== "undefined";
+ const hasPin = typeof pinUid !== "undefined";
const { muted, video, shareScreen } = voicingInfo;
return (
@@ -74,19 +84,19 @@ const VoiceFullscreen = ({ id, context }: Props) => {
{/* middle */}
- {members.map((uid, idx) => {
+ {members.map((uid) => {
const curr = userData[uid];
if (!curr) return null;
const { muted, speakingVolume = 0, shareScreen } = membersData[uid];
const speaking = speakingVolume > 50;
- const special = hasPin ? pin == idx : idx == speakingIndex;
+ const special = hasPin ? pinUid == uid : uid == speakingUid;
const disablePin = special && !hasPin;
- return -
+ return
-
{shareScreen ? : null}
- {!disablePin && }
+ {!disablePin &&
+
+
+ }
{curr?.name}
@@ -129,7 +142,7 @@ const VoiceFullscreen = ({ id, context }: Props) => {
-
-
+