refactor: video call
This commit is contained in:
@@ -71,7 +71,16 @@ export default async function handler({ operation, data = {}, payload }: Params)
|
|||||||
break;
|
break;
|
||||||
case "updateChannelVisibleAside":
|
case "updateChannelVisibleAside":
|
||||||
{
|
{
|
||||||
await table?.setItem("channelAsides", data.channelAsides);
|
if (payload.aside !== "voice") {
|
||||||
|
await table?.setItem("channelAsides", data.channelAsides);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "updateDMVisibleAside":
|
||||||
|
{
|
||||||
|
if (payload.aside !== "voice") {
|
||||||
|
await table?.setItem("dmAsides", data.dmAsides);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -124,11 +124,12 @@ export const serverApi = createApi({
|
|||||||
const { success } = resp;
|
const { success } = resp;
|
||||||
if (success) {
|
if (success) {
|
||||||
const arr = resp.data.channels.map(data => {
|
const arr = resp.data.channels.map(data => {
|
||||||
const [id] = data.channel_name.split(":").slice(-1);
|
const [type, id] = data.channel_name.split(":").slice(-2);
|
||||||
const count = data.user_count;
|
const count = data.user_count;
|
||||||
|
const context = type === "group" ? "channel" as const : "dm" as const;
|
||||||
return {
|
return {
|
||||||
id: +id,
|
id: +id,
|
||||||
context: "channel" as const,
|
context,
|
||||||
memberCount: count
|
memberCount: count
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -149,9 +150,11 @@ export const serverApi = createApi({
|
|||||||
getAgoraStatus: builder.query<boolean, void>({
|
getAgoraStatus: builder.query<boolean, void>({
|
||||||
query: () => ({ url: `/admin/agora/enabled` })
|
query: () => ({ url: `/admin/agora/enabled` })
|
||||||
}),
|
}),
|
||||||
getAgoraToken: builder.query<AgoraTokenResponse, number>({
|
generateAgoraToken: builder.mutation<AgoraTokenResponse, { uid: number } | { gid: number }>({
|
||||||
query: (id) => ({
|
query: (data) => ({
|
||||||
url: `group/${id}/agora_token`,
|
url: `/admin/agora/token`,
|
||||||
|
method: "POST",
|
||||||
|
body: data
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
getSystemCommon: builder.query<SystemCommon, void>({
|
getSystemCommon: builder.query<SystemCommon, void>({
|
||||||
@@ -396,11 +399,11 @@ export const {
|
|||||||
useSendMessageByBotMutation,
|
useSendMessageByBotMutation,
|
||||||
useUpdateFrontendUrlMutation,
|
useUpdateFrontendUrlMutation,
|
||||||
useGetFrontendUrlQuery,
|
useGetFrontendUrlQuery,
|
||||||
useLazyGetAgoraTokenQuery,
|
|
||||||
useGetAgoraConfigQuery,
|
useGetAgoraConfigQuery,
|
||||||
useGetAgoraStatusQuery,
|
useGetAgoraStatusQuery,
|
||||||
useGetAgoraChannelsQuery,
|
useGetAgoraChannelsQuery,
|
||||||
useUpdateSystemCommonMutation,
|
useUpdateSystemCommonMutation,
|
||||||
useLazyGetSystemCommonQuery,
|
useLazyGetSystemCommonQuery,
|
||||||
useGetSystemCommonQuery
|
useGetSystemCommonQuery,
|
||||||
|
useGenerateAgoraTokenMutation
|
||||||
} = serverApi;
|
} = serverApi;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { AutoDeleteMessageSettingDTO, AutoDeleteMsgForGroup, AutoDeleteMsgForUse
|
|||||||
import { resetAuthData } from "./auth.data";
|
import { resetAuthData } from "./auth.data";
|
||||||
|
|
||||||
type ChannelAside = "members" | "voice" | null;
|
type ChannelAside = "members" | "voice" | null;
|
||||||
|
type DMAside = "voice" | null;
|
||||||
export interface State {
|
export interface State {
|
||||||
og: { [url: string]: OG }
|
og: { [url: string]: OG }
|
||||||
usersVersion: number;
|
usersVersion: number;
|
||||||
@@ -18,6 +19,7 @@ export interface State {
|
|||||||
autoDeleteMsgUsers: AutoDeleteMsgForUser[];
|
autoDeleteMsgUsers: AutoDeleteMsgForUser[];
|
||||||
autoDeleteMsgChannels: AutoDeleteMsgForGroup[];
|
autoDeleteMsgChannels: AutoDeleteMsgForGroup[];
|
||||||
channelAsides: { [cid: number]: ChannelAside };
|
channelAsides: { [cid: number]: ChannelAside };
|
||||||
|
dmAsides: { [uid: number]: DMAside };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -33,7 +35,8 @@ const initialState: State = {
|
|||||||
muteChannels: {},
|
muteChannels: {},
|
||||||
autoDeleteMsgUsers: [],
|
autoDeleteMsgUsers: [],
|
||||||
autoDeleteMsgChannels: [],
|
autoDeleteMsgChannels: [],
|
||||||
channelAsides: {}
|
channelAsides: {},
|
||||||
|
dmAsides: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
const footprintSlice = createSlice({
|
const footprintSlice = createSlice({
|
||||||
@@ -56,7 +59,8 @@ const footprintSlice = createSlice({
|
|||||||
muteChannels = {},
|
muteChannels = {},
|
||||||
autoDeleteMsgUsers = [],
|
autoDeleteMsgUsers = [],
|
||||||
autoDeleteMsgChannels = [],
|
autoDeleteMsgChannels = [],
|
||||||
channelAsides = {}
|
channelAsides = {},
|
||||||
|
dmAsides = {}
|
||||||
} = action.payload;
|
} = action.payload;
|
||||||
return {
|
return {
|
||||||
og,
|
og,
|
||||||
@@ -70,7 +74,8 @@ const footprintSlice = createSlice({
|
|||||||
muteChannels,
|
muteChannels,
|
||||||
autoDeleteMsgUsers,
|
autoDeleteMsgUsers,
|
||||||
autoDeleteMsgChannels,
|
autoDeleteMsgChannels,
|
||||||
channelAsides
|
channelAsides,
|
||||||
|
dmAsides
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
updateUsersVersion(state, action: PayloadAction<number>) {
|
updateUsersVersion(state, action: PayloadAction<number>) {
|
||||||
@@ -189,6 +194,10 @@ const footprintSlice = createSlice({
|
|||||||
const { id, aside } = action.payload;
|
const { id, aside } = action.payload;
|
||||||
state.channelAsides[id] = aside;
|
state.channelAsides[id] = aside;
|
||||||
},
|
},
|
||||||
|
updateDMVisibleAside(state, action: PayloadAction<{ id: number, aside: DMAside }>) {
|
||||||
|
const { id, aside } = action.payload;
|
||||||
|
state.dmAsides[id] = aside;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder.addCase(resetAuthData, (state) => {
|
builder.addCase(resetAuthData, (state) => {
|
||||||
@@ -198,6 +207,11 @@ const footprintSlice = createSlice({
|
|||||||
state.channelAsides[+channel_id] = null;
|
state.channelAsides[+channel_id] = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Object.keys(state.dmAsides).forEach((uid: string) => {
|
||||||
|
if (state.dmAsides[+uid] === "voice") {
|
||||||
|
state.dmAsides[+uid] = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -213,7 +227,8 @@ export const {
|
|||||||
updateAutoDeleteSetting,
|
updateAutoDeleteSetting,
|
||||||
updateHistoryMark,
|
updateHistoryMark,
|
||||||
upsertOG,
|
upsertOG,
|
||||||
updateChannelVisibleAside
|
updateChannelVisibleAside,
|
||||||
|
updateDMVisibleAside
|
||||||
} = footprintSlice.actions;
|
} = footprintSlice.actions;
|
||||||
|
|
||||||
export default footprintSlice.reducer;
|
export default footprintSlice.reducer;
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import AgoraRTC, { ICameraVideoTrack, IMicrophoneAudioTrack } from 'agora-rtc-sdk-ng';
|
import AgoraRTC, { ICameraVideoTrack, IMicrophoneAudioTrack } from 'agora-rtc-sdk-ng';
|
||||||
import { memo, useEffect } from 'react';
|
import { memo, useEffect } from 'react';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { useGetAgoraChannelsQuery, useGetAgoraStatusQuery, useLazyGetAgoraTokenQuery } from '../../app/services/server';
|
import { useGetAgoraChannelsQuery, useGetAgoraStatusQuery, useGenerateAgoraTokenMutation } from '../../app/services/server';
|
||||||
import { updateChannelVisibleAside } from '../../app/slices/footprint';
|
import { updateChannelVisibleAside } from '../../app/slices/footprint';
|
||||||
import { addVoiceMember, removeVoiceMember, updateConnectionState, updateDeafenStatus, updateMuteStatus, updateVoicingInfo, updateVoicingMember, updateVoicingNetworkQuality, upsertVoiceList } from '../../app/slices/voice';
|
import { addVoiceMember, removeVoiceMember, updateConnectionState, updateDeafenStatus, updateMuteStatus, updateVoicingInfo, updateVoicingMember, updateVoicingNetworkQuality, upsertVoiceList } from '../../app/slices/voice';
|
||||||
import { useAppSelector } from '../../app/store';
|
import { useAppSelector } from '../../app/store';
|
||||||
import AudioJoin from '../../assets/join.wav';
|
import AudioJoin from '../../assets/join.wav';
|
||||||
|
import { playAgoraVideo } from '../utils';
|
||||||
// import { compareVersion } from '../utils';
|
// import { compareVersion } from '../utils';
|
||||||
// type Props = {}
|
// type Props = {}
|
||||||
AgoraRTC.setLogLevel(process.env.NODE_ENV === 'development' ? 0 : 4);
|
AgoraRTC.setLogLevel(process.env.NODE_ENV === 'development' ? 0 : 4);
|
||||||
@@ -42,12 +43,8 @@ const Voice = () => {
|
|||||||
window.VOICE_TRACK_MAP[+user.uid] = user.audioTrack;
|
window.VOICE_TRACK_MAP[+user.uid] = user.audioTrack;
|
||||||
}
|
}
|
||||||
if (mediaType == "video") {
|
if (mediaType == "video") {
|
||||||
const playerEle = document.querySelector(`#CAMERA_${user.uid}`) as HTMLElement;
|
|
||||||
if (playerEle) {
|
|
||||||
playerEle.classList.add("h-[120px]");
|
|
||||||
user.videoTrack?.play(playerEle);
|
|
||||||
}
|
|
||||||
window.VIDEO_TRACK_MAP[+user.uid] = user.videoTrack;
|
window.VIDEO_TRACK_MAP[+user.uid] = user.videoTrack;
|
||||||
|
playAgoraVideo(+user.uid);
|
||||||
}
|
}
|
||||||
agoraEngine.on("user-unpublished", (user) => {
|
agoraEngine.on("user-unpublished", (user) => {
|
||||||
if (!user.hasAudio) {
|
if (!user.hasAudio) {
|
||||||
@@ -112,14 +109,14 @@ const Voice = () => {
|
|||||||
});
|
});
|
||||||
// 有新用户加入
|
// 有新用户加入
|
||||||
agoraEngine.on("user-joined", async (user) => {
|
agoraEngine.on("user-joined", async (user) => {
|
||||||
console.log(user.uid, !!localAudioTrack, agoraEngine.channelName, " has joined the channel");
|
console.log(user.uid, agoraEngine.channelName, " has joined the channel");
|
||||||
dispatch(addVoiceMember(+user.uid));
|
dispatch(addVoiceMember(+user.uid));
|
||||||
});
|
});
|
||||||
window.VOICE_CLIENT = agoraEngine;
|
window.VOICE_CLIENT = agoraEngine;
|
||||||
};
|
};
|
||||||
const handlePageUnload = (evt: BeforeUnloadEvent) => {
|
const handlePageUnload = (evt: BeforeUnloadEvent) => {
|
||||||
console.log("unload");
|
console.log("unload");
|
||||||
if (localAudioTrack) {
|
if (window.VOICE_CLIENT?.connectionState === "CONNECTED") {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
return (evt.returnValue = "");
|
return (evt.returnValue = "");
|
||||||
}
|
}
|
||||||
@@ -131,14 +128,15 @@ const Voice = () => {
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("beforeunload", handlePageUnload, { capture: true });
|
window.removeEventListener("beforeunload", handlePageUnload, { capture: true });
|
||||||
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
let localAudioTrack: IMicrophoneAudioTrack | null = null;
|
// let localAudioTrack: IMicrophoneAudioTrack | null = null;
|
||||||
let localVideoTrack: ICameraVideoTrack | null = null;
|
// let localVideoTrack: ICameraVideoTrack | null = null;
|
||||||
type VoiceProps = {
|
type VoiceProps = {
|
||||||
id: number,
|
id: number,
|
||||||
context?: "channel" | "dm"
|
context?: "channel" | "dm"
|
||||||
@@ -148,11 +146,11 @@ const useVoice = ({ id, context = "channel" }: VoiceProps) => {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { voicingInfo, loginUid } = useAppSelector(store => {
|
const { voicingInfo, loginUid } = useAppSelector(store => {
|
||||||
return {
|
return {
|
||||||
loginUid: store.authData.user?.uid,
|
loginUid: store.authData.user?.uid ?? 0,
|
||||||
voicingInfo: store.voice.voicing
|
voicingInfo: store.voice.voicing
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const [generateToken] = useLazyGetAgoraTokenQuery();
|
const [generateToken] = useGenerateAgoraTokenMutation();
|
||||||
// const [joining, setJoining] = useState(false);
|
// const [joining, setJoining] = useState(false);
|
||||||
const joinVoice = async () => {
|
const joinVoice = async () => {
|
||||||
// setJoining(true);
|
// setJoining(true);
|
||||||
@@ -161,14 +159,21 @@ const useVoice = ({ id, context = "channel" }: VoiceProps) => {
|
|||||||
context,
|
context,
|
||||||
joining: true
|
joining: true
|
||||||
}));
|
}));
|
||||||
const { isError, data } = await generateToken(id);
|
const resp = await generateToken(context == "channel" ? { gid: id } : { uid: id });
|
||||||
if (!isError && data) {
|
if ('error' in resp) {
|
||||||
const { channel_name, app_id, agora_token, uid } = data;
|
console.error("generate agora token error");
|
||||||
|
dispatch(updateVoicingInfo({
|
||||||
|
joining: false,
|
||||||
|
id,
|
||||||
|
context,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
console.table(resp.data);
|
||||||
|
const { channel_name, app_id, agora_token, uid } = resp.data;
|
||||||
if (window.VOICE_CLIENT) {
|
if (window.VOICE_CLIENT) {
|
||||||
await window.VOICE_CLIENT.join(app_id, channel_name, agora_token, uid);
|
await window.VOICE_CLIENT.join(app_id, channel_name, agora_token, uid);
|
||||||
console.table(data);
|
|
||||||
// Create a local audio track from the microphone audio.
|
// Create a local audio track from the microphone audio.
|
||||||
localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
|
const localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
|
||||||
// Publish the local audio track in the channel.
|
// Publish the local audio track in the channel.
|
||||||
await window.VOICE_CLIENT.publish(localAudioTrack);
|
await window.VOICE_CLIENT.publish(localAudioTrack);
|
||||||
// play the join audio
|
// play the join audio
|
||||||
@@ -183,37 +188,31 @@ const useVoice = ({ id, context = "channel" }: VoiceProps) => {
|
|||||||
}));
|
}));
|
||||||
// 把自己加进去
|
// 把自己加进去
|
||||||
dispatch(addVoiceMember(uid));
|
dispatch(addVoiceMember(uid));
|
||||||
|
// 放到全局变量里
|
||||||
|
window.VOICE_TRACK_MAP[loginUid] = localAudioTrack;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
console.error("generate agora token error");
|
|
||||||
dispatch(updateVoicingInfo({
|
|
||||||
joining: false,
|
|
||||||
id,
|
|
||||||
context,
|
|
||||||
}));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
// setJoining(false);
|
// setJoining(false);
|
||||||
};
|
};
|
||||||
const openCamera = async () => {
|
const openCamera = async () => {
|
||||||
localVideoTrack = await AgoraRTC.createCameraVideoTrack();
|
const localVideoTrack = await AgoraRTC.createCameraVideoTrack();
|
||||||
await window.VOICE_CLIENT?.publish(localVideoTrack);
|
await window.VOICE_CLIENT?.publish(localVideoTrack);
|
||||||
const playerEle = document.querySelector(`#CAMERA_${loginUid}`) as HTMLElement;
|
|
||||||
if (playerEle) {
|
|
||||||
playerEle.classList.add("h-[120px]");
|
|
||||||
localVideoTrack?.play(playerEle);
|
|
||||||
}
|
|
||||||
dispatch(updateVoicingInfo({
|
dispatch(updateVoicingInfo({
|
||||||
video: true,
|
video: true,
|
||||||
id,
|
id,
|
||||||
context,
|
context,
|
||||||
}));
|
}));
|
||||||
|
// 放到全局变量里
|
||||||
|
window.VIDEO_TRACK_MAP[loginUid] = localVideoTrack;
|
||||||
|
playAgoraVideo(loginUid);
|
||||||
};
|
};
|
||||||
const closeCamera = async () => {
|
const closeCamera = async () => {
|
||||||
|
const localVideoTrack = window.VIDEO_TRACK_MAP[loginUid] as ICameraVideoTrack;
|
||||||
if (localVideoTrack) {
|
if (localVideoTrack) {
|
||||||
await window.VOICE_CLIENT?.unpublish(localVideoTrack);
|
await window.VOICE_CLIENT?.unpublish(localVideoTrack);
|
||||||
localVideoTrack.close();
|
localVideoTrack.close();
|
||||||
localVideoTrack = null;
|
window.VIDEO_TRACK_MAP[loginUid] = null;
|
||||||
// 关闭视频后,需要把视频的高度设置回去
|
// 关闭视频后,需要把视频的高度设置回去
|
||||||
const playerEle = document.querySelector(`#CAMERA_${loginUid}`) as HTMLElement;
|
const playerEle = document.querySelector(`#CAMERA_${loginUid}`) as HTMLElement;
|
||||||
playerEle.classList.remove("h-[120px]");
|
playerEle.classList.remove("h-[120px]");
|
||||||
@@ -226,12 +225,14 @@ const useVoice = ({ id, context = "channel" }: VoiceProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const leave = async () => {
|
const leave = async () => {
|
||||||
|
const localAudioTrack = window.VOICE_TRACK_MAP[loginUid] as IMicrophoneAudioTrack;
|
||||||
|
const localVideoTrack = window.VIDEO_TRACK_MAP[loginUid] as ICameraVideoTrack;
|
||||||
if (window.VOICE_CLIENT && localAudioTrack) {
|
if (window.VOICE_CLIENT && localAudioTrack) {
|
||||||
localAudioTrack.close();
|
localAudioTrack.close();
|
||||||
localAudioTrack = null;
|
window.VOICE_TRACK_MAP[loginUid] = null;
|
||||||
if (localVideoTrack) {
|
if (localVideoTrack) {
|
||||||
localVideoTrack.close();
|
localVideoTrack.close();
|
||||||
localVideoTrack = null;
|
window.VIDEO_TRACK_MAP[loginUid] = null;
|
||||||
}
|
}
|
||||||
await window.VOICE_CLIENT.leave();
|
await window.VOICE_CLIENT.leave();
|
||||||
dispatch(updateVoicingInfo(null));
|
dispatch(updateVoicingInfo(null));
|
||||||
@@ -249,12 +250,14 @@ const useVoice = ({ id, context = "channel" }: VoiceProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const setMute = (mute: boolean) => {
|
const setMute = (mute: boolean) => {
|
||||||
|
const localAudioTrack = window.VOICE_TRACK_MAP[loginUid] as IMicrophoneAudioTrack;
|
||||||
if (localAudioTrack) {
|
if (localAudioTrack) {
|
||||||
localAudioTrack.setMuted(mute);
|
localAudioTrack.setMuted(mute);
|
||||||
dispatch(updateMuteStatus(mute));
|
dispatch(updateMuteStatus(mute));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const setDeafen = (deafen: boolean) => {
|
const setDeafen = (deafen: boolean) => {
|
||||||
|
const localAudioTrack = window.VOICE_TRACK_MAP[loginUid] as IMicrophoneAudioTrack;
|
||||||
if (localAudioTrack) {
|
if (localAudioTrack) {
|
||||||
if (deafen) {
|
if (deafen) {
|
||||||
localAudioTrack.setMuted(true);
|
localAudioTrack.setMuted(true);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import BASE_URL, { FILE_IMAGE_SIZE, ContentTypes, KEY_TOKEN, KEY_REFRESH_TOKEN, KEY_EXPIRE } from "../app/config";
|
import BASE_URL, { FILE_IMAGE_SIZE, ContentTypes, KEY_TOKEN, KEY_REFRESH_TOKEN, KEY_EXPIRE } from "../app/config";
|
||||||
|
import { ICameraVideoTrack } from "agora-rtc-sdk-ng";
|
||||||
import IconPdf from "../assets/icons/file.pdf.svg";
|
import IconPdf from "../assets/icons/file.pdf.svg";
|
||||||
import IconAudio from "../assets/icons/file.audio.svg";
|
import IconAudio from "../assets/icons/file.audio.svg";
|
||||||
import IconVideo from "../assets/icons/file.video.svg";
|
import IconVideo from "../assets/icons/file.video.svg";
|
||||||
@@ -371,4 +372,16 @@ export const fromNowTime = (ts?: number) => {
|
|||||||
if (!ts) return null;
|
if (!ts) return null;
|
||||||
const currTS = + new Date();
|
const currTS = + new Date();
|
||||||
return dayjs(ts > currTS ? currTS : ts).fromNow();
|
return dayjs(ts > currTS ? currTS : ts).fromNow();
|
||||||
|
};
|
||||||
|
export const playAgoraVideo = (uid: number, videoTrack?: ICameraVideoTrack | null) => {
|
||||||
|
if (!videoTrack && !window.VIDEO_TRACK_MAP[uid]) return;
|
||||||
|
const playerEle = document.querySelector(`#CAMERA_${uid}`) as HTMLElement;
|
||||||
|
if (playerEle) {
|
||||||
|
playerEle.classList.add("h-[120px]");
|
||||||
|
if (videoTrack) {
|
||||||
|
videoTrack.play(playerEle);
|
||||||
|
} else {
|
||||||
|
window.VIDEO_TRACK_MAP[uid]?.play(playerEle);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
@@ -8,6 +8,8 @@ import User from "../../../common/component/User";
|
|||||||
import Layout from "../Layout";
|
import Layout from "../Layout";
|
||||||
import { useAppSelector } from "../../../app/store";
|
import { useAppSelector } from "../../../app/store";
|
||||||
import GoBackNav from "../../../common/component/GoBackNav";
|
import GoBackNav from "../../../common/component/GoBackNav";
|
||||||
|
import ServerVersionChecker from "../../../common/component/ServerVersionChecker";
|
||||||
|
import VoiceChat from "../VoiceChat";
|
||||||
type Props = {
|
type Props = {
|
||||||
uid: number;
|
uid: number;
|
||||||
dropFiles?: File[];
|
dropFiles?: File[];
|
||||||
@@ -33,6 +35,9 @@ const DMChat: FC<Props> = ({ uid = 0, dropFiles }) => {
|
|||||||
dropFiles={dropFiles}
|
dropFiles={dropFiles}
|
||||||
aside={
|
aside={
|
||||||
<ul className="flex flex-col gap-6">
|
<ul className="flex flex-col gap-6">
|
||||||
|
<ServerVersionChecker version="0.3.6" empty={true}>
|
||||||
|
<VoiceChat context={`dm`} id={uid} />
|
||||||
|
</ServerVersionChecker>
|
||||||
<Tooltip tip="Saved Items" placement="left">
|
<Tooltip tip="Saved Items" placement="left">
|
||||||
<Tippy
|
<Tippy
|
||||||
placement="left-start"
|
placement="left-start"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
// import React from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { VoicingInfo } from '../../../app/slices/voice';
|
import { VoicingInfo } from '../../../app/slices/voice';
|
||||||
@@ -15,6 +15,7 @@ import IconScreen from '../../../assets/icons/share.screen.svg';
|
|||||||
import StyledButton from '../../../common/component/styled/Button';
|
import StyledButton from '../../../common/component/styled/Button';
|
||||||
import IconCallOff from '../../../assets/icons/call.off.svg';
|
import IconCallOff from '../../../assets/icons/call.off.svg';
|
||||||
import Tooltip from '../../../common/component/Tooltip';
|
import Tooltip from '../../../common/component/Tooltip';
|
||||||
|
import { playAgoraVideo } from '../../../common/utils';
|
||||||
// import User from '../../../common/component/User';
|
// import User from '../../../common/component/User';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -34,6 +35,13 @@ const VoiceManagement = ({ info, setMute, setDeafen, leave, closeCamera, openCam
|
|||||||
voicingMembers: store.voice.voicingMembers
|
voicingMembers: store.voice.voicingMembers
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
const ids = voicingMembers.ids;
|
||||||
|
ids.forEach(id => {
|
||||||
|
playAgoraVideo(id);
|
||||||
|
});
|
||||||
|
}, [voicingMembers.ids]);
|
||||||
|
|
||||||
if (!info) return null;
|
if (!info) return null;
|
||||||
const { deafen, muted, video } = info;
|
const { deafen, muted, video } = info;
|
||||||
const nameClass = clsx(`text-sm text-gray-500 max-w-[120px] truncate font-semibold dark:text-white`);
|
const nameClass = clsx(`text-sm text-gray-500 max-w-[120px] truncate font-semibold dark:text-white`);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// import { useState } from 'react';
|
// import { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { updateChannelVisibleAside } from '../../../app/slices/footprint';
|
import { updateChannelVisibleAside, updateDMVisibleAside } from '../../../app/slices/footprint';
|
||||||
import { useAppSelector } from '../../../app/store';
|
import { useAppSelector } from '../../../app/store';
|
||||||
import IconHeadphone from '../../../assets/icons/headphone.svg';
|
import IconHeadphone from '../../../assets/icons/headphone.svg';
|
||||||
import Tooltip from '../../../common/component/Tooltip';
|
import Tooltip from '../../../common/component/Tooltip';
|
||||||
@@ -29,13 +29,11 @@ const VoiceChat = ({ id, context = "channel" }: Props) => {
|
|||||||
const { data: enabled } = useGetAgoraStatusQuery();
|
const { data: enabled } = useGetAgoraStatusQuery();
|
||||||
const { t } = useTranslation("chat");
|
const { t } = useTranslation("chat");
|
||||||
const toggleDashboard = () => {
|
const toggleDashboard = () => {
|
||||||
if (context == "channel") {
|
const data = {
|
||||||
dispatch(updateChannelVisibleAside({
|
id,
|
||||||
id,
|
aside: visibleAside == "voice" ? null : "voice" as const
|
||||||
aside: visibleAside == "voice" ? null : "voice"
|
};
|
||||||
}));
|
dispatch(context == "channel" ? updateChannelVisibleAside(data) : updateDMVisibleAside(data));
|
||||||
}
|
|
||||||
// todo DM
|
|
||||||
};
|
};
|
||||||
const handleJoin = () => {
|
const handleJoin = () => {
|
||||||
if (joining || joined) {
|
if (joining || joined) {
|
||||||
@@ -43,12 +41,11 @@ const VoiceChat = ({ id, context = "channel" }: Props) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
joinVoice();
|
joinVoice();
|
||||||
if (context == "channel") {
|
const data = {
|
||||||
dispatch(updateChannelVisibleAside({
|
id,
|
||||||
id,
|
aside: "voice" as const
|
||||||
aside: "voice"
|
};
|
||||||
}));
|
dispatch(context == "channel" ? updateChannelVisibleAside(data) : updateDMVisibleAside(data));
|
||||||
}
|
|
||||||
};
|
};
|
||||||
if (!loginUser || !enabled) return null;
|
if (!loginUser || !enabled) return null;
|
||||||
const visible = visibleAside == "voice";
|
const visible = visibleAside == "voice";
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export default function ChannelSetting() {
|
|||||||
pathPrefix={`/setting/channel/${cid}`}
|
pathPrefix={`/setting/channel/${cid}`}
|
||||||
nav={currNav}
|
nav={currNav}
|
||||||
closeModal={close}
|
closeModal={close}
|
||||||
title="Channel Setting"
|
title="Channel Settings"
|
||||||
navs={navs}
|
navs={navs}
|
||||||
dangers={[
|
dangers={[
|
||||||
canLeave && {
|
canLeave && {
|
||||||
|
|||||||
Reference in New Issue
Block a user