refactor: video call
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vocechat-web",
|
"name": "vocechat-web",
|
||||||
"version": "0.3.57",
|
"version": "0.3.58",
|
||||||
"private": true,
|
"private": true,
|
||||||
"homepage": "https://voce.chat",
|
"homepage": "https://voce.chat",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -52,5 +52,7 @@
|
|||||||
"undeafen": "Undeafen",
|
"undeafen": "Undeafen",
|
||||||
"mute": "Mute",
|
"mute": "Mute",
|
||||||
"unmute": "Unmute",
|
"unmute": "Unmute",
|
||||||
|
"camera_on": "Turn on camera",
|
||||||
|
"camera_off": "Turn off camera",
|
||||||
"leave_voice": "Leave Voice Chat"
|
"leave_voice": "Leave Voice Chat"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,5 +53,7 @@
|
|||||||
"undeafen": "取消静默",
|
"undeafen": "取消静默",
|
||||||
"mute": "静音",
|
"mute": "静音",
|
||||||
"unmute": "取消静音",
|
"unmute": "取消静音",
|
||||||
|
"camera_on": "开启摄像头",
|
||||||
|
"camera_off": "关闭摄像头",
|
||||||
"leave_voice": "离开"
|
"leave_voice": "离开"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export type VoicingInfo = {
|
|||||||
downlinkNetworkQuality?: number,
|
downlinkNetworkQuality?: number,
|
||||||
muted?: boolean,
|
muted?: boolean,
|
||||||
deafen?: boolean,
|
deafen?: boolean,
|
||||||
|
video?: boolean,
|
||||||
joining?: boolean,
|
joining?: boolean,
|
||||||
connectionState?: ConnectionState
|
connectionState?: ConnectionState
|
||||||
} & VoiceBasicInfo
|
} & VoiceBasicInfo
|
||||||
@@ -19,7 +20,8 @@ export type VoicingInfo = {
|
|||||||
export type VoicingMemberInfo = {
|
export type VoicingMemberInfo = {
|
||||||
speakingVolume?: number,
|
speakingVolume?: number,
|
||||||
muted?: boolean,
|
muted?: boolean,
|
||||||
deafen?: boolean
|
deafen?: boolean,
|
||||||
|
video?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type VoicingMembers = {
|
export type VoicingMembers = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import AgoraRTC, { 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, useLazyGetAgoraTokenQuery } from '../../app/services/server';
|
||||||
@@ -10,16 +10,14 @@ import AudioJoin from '../../assets/join.wav';
|
|||||||
// type Props = {}
|
// type Props = {}
|
||||||
AgoraRTC.setLogLevel(process.env.NODE_ENV === 'development' ? 0 : 4);
|
AgoraRTC.setLogLevel(process.env.NODE_ENV === 'development' ? 0 : 4);
|
||||||
window.VOICE_TRACK_MAP = window.VOICE_TRACK_MAP ?? {};
|
window.VOICE_TRACK_MAP = window.VOICE_TRACK_MAP ?? {};
|
||||||
|
window.VIDEO_TRACK_MAP = window.VIDEO_TRACK_MAP ?? {};
|
||||||
// let tmpUids: number[] = [];
|
// let tmpUids: number[] = [];
|
||||||
const Voice = () => {
|
const Voice = () => {
|
||||||
const { serverVersion } = useAppSelector(store => {
|
// const { serverVersion } = useAppSelector(store => {
|
||||||
return {
|
// return {
|
||||||
serverVersion: store.server.version ?? 0,
|
// serverVersion: store.server.version ?? 0,
|
||||||
// joined: !!store.voice.voicing
|
// };
|
||||||
};
|
// });
|
||||||
});
|
|
||||||
console.log("serverVersion", serverVersion);
|
|
||||||
|
|
||||||
// const skipAgoraStatusCheck = compareVersion(serverVersion, '0.3.5') >= 0;
|
// const skipAgoraStatusCheck = compareVersion(serverVersion, '0.3.5') >= 0;
|
||||||
const { data: enabled } = useGetAgoraStatusQuery();
|
const { data: enabled } = useGetAgoraStatusQuery();
|
||||||
useGetAgoraChannelsQuery({ page_no: 0, page_size: 100 }, {
|
useGetAgoraChannelsQuery({ page_no: 0, page_size: 100 }, {
|
||||||
@@ -44,16 +42,25 @@ 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 id = `CAMERA_${user.uid}`;
|
const playerEle = document.querySelector(`#CAMERA_${user.uid}`) as HTMLElement;
|
||||||
console.log("video container id", id);
|
if (playerEle) {
|
||||||
|
playerEle.classList.add("h-[120px]");
|
||||||
// 播放远端视频
|
user.videoTrack?.play(playerEle);
|
||||||
user.videoTrack?.play(id);
|
}
|
||||||
window.VOICE_TRACK_MAP[+user.uid] = user.videoTrack;
|
window.VIDEO_TRACK_MAP[+user.uid] = user.videoTrack;
|
||||||
}
|
}
|
||||||
agoraEngine.on("user-unpublished", (user) => {
|
agoraEngine.on("user-unpublished", (user) => {
|
||||||
|
if (!user.hasAudio) {
|
||||||
// 远端用户取消了音频(muted)
|
// 远端用户取消了音频(muted)
|
||||||
dispatch(updateVoicingMember({ uid: +user.uid, info: { muted: true } }));
|
dispatch(updateVoicingMember({ uid: +user.uid, info: { muted: true } }));
|
||||||
|
}
|
||||||
|
if (!user.hasVideo) {
|
||||||
|
// 远端用户取消了视频
|
||||||
|
dispatch(updateVoicingMember({ uid: +user.uid, info: { video: false } }));
|
||||||
|
// 关闭视频后,需要把视频的高度设置回去
|
||||||
|
const playerEle = document.querySelector(`#CAMERA_${user.uid}`) as HTMLElement;
|
||||||
|
playerEle.classList.remove("h-[120px]");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
//remote user leave
|
//remote user leave
|
||||||
agoraEngine.on("user-left", (user, reason) => {
|
agoraEngine.on("user-left", (user, reason) => {
|
||||||
@@ -105,14 +112,14 @@ const Voice = () => {
|
|||||||
});
|
});
|
||||||
// 有新用户加入
|
// 有新用户加入
|
||||||
agoraEngine.on("user-joined", async (user) => {
|
agoraEngine.on("user-joined", async (user) => {
|
||||||
console.log(user.uid, !!localTrack, agoraEngine.channelName, " has joined the channel");
|
console.log(user.uid, !!localAudioTrack, 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 (localTrack) {
|
if (localAudioTrack) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
return (evt.returnValue = "");
|
return (evt.returnValue = "");
|
||||||
}
|
}
|
||||||
@@ -130,7 +137,8 @@ const Voice = () => {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
let localTrack: IMicrophoneAudioTrack | null = null;
|
let localAudioTrack: IMicrophoneAudioTrack | null = null;
|
||||||
|
let localVideoTrack: ICameraVideoTrack | null = null;
|
||||||
type VoiceProps = {
|
type VoiceProps = {
|
||||||
id: number,
|
id: number,
|
||||||
context?: "channel" | "dm"
|
context?: "channel" | "dm"
|
||||||
@@ -160,9 +168,9 @@ const useVoice = ({ id, context = "channel" }: VoiceProps) => {
|
|||||||
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);
|
console.table(data);
|
||||||
// Create a local audio track from the microphone audio.
|
// Create a local audio track from the microphone audio.
|
||||||
localTrack = await AgoraRTC.createMicrophoneAudioTrack();
|
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(localTrack);
|
await window.VOICE_CLIENT.publish(localAudioTrack);
|
||||||
// play the join audio
|
// play the join audio
|
||||||
audioJoin.play();
|
audioJoin.play();
|
||||||
console.log("Publish success!,joined the channel");
|
console.log("Publish success!,joined the channel");
|
||||||
@@ -188,28 +196,43 @@ const useVoice = ({ id, context = "channel" }: VoiceProps) => {
|
|||||||
// setJoining(false);
|
// setJoining(false);
|
||||||
};
|
};
|
||||||
const openCamera = async () => {
|
const openCamera = async () => {
|
||||||
if (localTrack) {
|
localVideoTrack = await AgoraRTC.createCameraVideoTrack();
|
||||||
localTrack.close();
|
await window.VOICE_CLIENT?.publish(localVideoTrack);
|
||||||
localTrack = null;
|
const playerEle = document.querySelector(`#CAMERA_${loginUid}`) as HTMLElement;
|
||||||
|
if (playerEle) {
|
||||||
|
playerEle.classList.add("h-[120px]");
|
||||||
|
localVideoTrack?.play(playerEle);
|
||||||
}
|
}
|
||||||
localTrack = await AgoraRTC.createCameraVideoTrack();
|
dispatch(updateVoicingInfo({
|
||||||
await window.VOICE_CLIENT?.publish(localTrack);
|
video: true,
|
||||||
const id = `CAMERA_${loginUid}`;
|
id,
|
||||||
localTrack?.play(id);
|
context,
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
const closeCamera = async () => {
|
const closeCamera = async () => {
|
||||||
if (localTrack) {
|
if (localVideoTrack) {
|
||||||
localTrack.close();
|
await window.VOICE_CLIENT?.unpublish(localVideoTrack);
|
||||||
localTrack = null;
|
localVideoTrack.close();
|
||||||
|
localVideoTrack = null;
|
||||||
|
// 关闭视频后,需要把视频的高度设置回去
|
||||||
|
const playerEle = document.querySelector(`#CAMERA_${loginUid}`) as HTMLElement;
|
||||||
|
playerEle.classList.remove("h-[120px]");
|
||||||
|
dispatch(updateVoicingInfo({
|
||||||
|
video: false,
|
||||||
|
id,
|
||||||
|
context,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
localTrack = await AgoraRTC.createMicrophoneAudioTrack();
|
|
||||||
await window.VOICE_CLIENT?.publish(localTrack);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const leave = async () => {
|
const leave = async () => {
|
||||||
if (window.VOICE_CLIENT && localTrack) {
|
if (window.VOICE_CLIENT && localAudioTrack) {
|
||||||
localTrack.close();
|
localAudioTrack.close();
|
||||||
localTrack = null;
|
localAudioTrack = null;
|
||||||
|
if (localVideoTrack) {
|
||||||
|
localVideoTrack.close();
|
||||||
|
localVideoTrack = null;
|
||||||
|
}
|
||||||
await window.VOICE_CLIENT.leave();
|
await window.VOICE_CLIENT.leave();
|
||||||
dispatch(updateVoicingInfo(null));
|
dispatch(updateVoicingInfo(null));
|
||||||
if (context == "channel") {
|
if (context == "channel") {
|
||||||
@@ -226,21 +249,21 @@ const useVoice = ({ id, context = "channel" }: VoiceProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const setMute = (mute: boolean) => {
|
const setMute = (mute: boolean) => {
|
||||||
if (localTrack) {
|
if (localAudioTrack) {
|
||||||
localTrack.setMuted(mute);
|
localAudioTrack.setMuted(mute);
|
||||||
dispatch(updateMuteStatus(mute));
|
dispatch(updateMuteStatus(mute));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const setDeafen = (deafen: boolean) => {
|
const setDeafen = (deafen: boolean) => {
|
||||||
if (localTrack) {
|
if (localAudioTrack) {
|
||||||
if (deafen) {
|
if (deafen) {
|
||||||
localTrack.setMuted(true);
|
localAudioTrack.setMuted(true);
|
||||||
// 远端音频,全部静音
|
// 远端音频,全部静音
|
||||||
Object.entries(window.VOICE_TRACK_MAP).forEach(([, audioTrack]) => {
|
Object.entries(window.VOICE_TRACK_MAP).forEach(([, audioTrack]) => {
|
||||||
audioTrack?.setVolume(0);
|
audioTrack?.setVolume(0);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
localTrack.setMuted(false);
|
localAudioTrack.setMuted(false);
|
||||||
// 远端音频,恢复原音
|
// 远端音频,恢复原音
|
||||||
Object.entries(window.VOICE_TRACK_MAP).forEach(([, audioTrack]) => {
|
Object.entries(window.VOICE_TRACK_MAP).forEach(([, audioTrack]) => {
|
||||||
audioTrack?.setVolume(100);
|
audioTrack?.setVolume(100);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import IconMicOff from '../../assets/icons/mic.off.svg';
|
|||||||
import IconCallOff from '../../assets/icons/headphone.svg';
|
import IconCallOff from '../../assets/icons/headphone.svg';
|
||||||
import IconSoundOn from '../../assets/icons/sound.on.svg';
|
import IconSoundOn from '../../assets/icons/sound.on.svg';
|
||||||
import IconSoundOff from '../../assets/icons/sound.off.svg';
|
import IconSoundOff from '../../assets/icons/sound.off.svg';
|
||||||
import IconCameraOff from '../../assets/icons/camera.off.svg';
|
// import IconCameraOff from '../../assets/icons/camera.off.svg';
|
||||||
import IconCamera from '../../assets/icons/camera.svg';
|
import IconCamera from '../../assets/icons/camera.svg';
|
||||||
import IconScreen from '../../assets/icons/share.screen.svg';
|
import IconScreen from '../../assets/icons/share.screen.svg';
|
||||||
import { useVoice } from '../../common/component/Voice';
|
import { useVoice } from '../../common/component/Voice';
|
||||||
@@ -22,7 +22,7 @@ type Props = {
|
|||||||
|
|
||||||
const RTCWidget = ({ id, context = "channel" }: Props) => {
|
const RTCWidget = ({ id, context = "channel" }: Props) => {
|
||||||
const { t } = useTranslation("chat");
|
const { t } = useTranslation("chat");
|
||||||
const { leave, voicingInfo, setMute, setDeafen, joining = true, openCamera } = useVoice({ context, id });
|
const { leave, voicingInfo, setMute, setDeafen, joining = true, openCamera, closeCamera } = useVoice({ context, id });
|
||||||
const { loginUser, channelData, userData } = useAppSelector(store => {
|
const { loginUser, channelData, userData } = useAppSelector(store => {
|
||||||
return {
|
return {
|
||||||
userData: store.users.byId,
|
userData: store.users.byId,
|
||||||
@@ -38,7 +38,6 @@ const RTCWidget = ({ id, context = "channel" }: Props) => {
|
|||||||
<div className='bg-gray-100 dark:bg-gray-900 flex flex-col p-2 rounded-3xl m-3 mb-4 text-sm'>
|
<div className='bg-gray-100 dark:bg-gray-900 flex flex-col p-2 rounded-3xl m-3 mb-4 text-sm'>
|
||||||
<div className="border-b border-b-gray-200 dark:border-b-gray-800 pb-2">
|
<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 justify-between items-center">
|
||||||
|
|
||||||
<div className="flex flex-1 items-center gap-1">
|
<div className="flex flex-1 items-center gap-1">
|
||||||
<Signal strength={voicingInfo.downlinkNetworkQuality} />
|
<Signal strength={voicingInfo.downlinkNetworkQuality} />
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
@@ -51,8 +50,8 @@ const RTCWidget = ({ id, context = "channel" }: Props) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 mt-3">
|
<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'>
|
<button onClick={voicingInfo.video ? closeCamera : openCamera} className={clsx('rounded-lg py-2 px-5 flex items-center gap-1', voicingInfo.video ? "bg-green-700" : "bg-white/50 dark:bg-black/50")}>
|
||||||
<IconCameraOff className="fill-gray-800 dark:fill-gray-200 w-6 h-6" />
|
<IconCamera 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>
|
<span className='text-sm text-gray-800 dark:text-gray-200 font-semibold'>Video</span>
|
||||||
</button>
|
</button>
|
||||||
<button className='rounded-lg py-2 px-5 bg-white/50 dark:bg-black/50 flex items-center gap-1'>
|
<button className='rounded-lg py-2 px-5 bg-white/50 dark:bg-black/50 flex items-center gap-1'>
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Dashboard = ({ context = "channel", id, visible }: Props) => {
|
const Dashboard = ({ context = "channel", id, visible }: Props) => {
|
||||||
const { voicingInfo, setMute, setDeafen, leave } = useVoice({ id, context });
|
const { voicingInfo, setMute, setDeafen, leave, closeCamera, openCamera } = useVoice({ id, context });
|
||||||
// const dispatch = useDispatch();
|
// const dispatch = useDispatch();
|
||||||
|
|
||||||
|
|
||||||
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"}`}>
|
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} />
|
<VoiceManagement info={voicingInfo} setMute={setMute} setDeafen={setDeafen} leave={leave} closeCamera={closeCamera} openCamera={openCamera} />
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,10 +21,12 @@ type Props = {
|
|||||||
info: VoicingInfo | null,
|
info: VoicingInfo | null,
|
||||||
setMute: (param: boolean) => void,
|
setMute: (param: boolean) => void,
|
||||||
setDeafen: (param: boolean) => void,
|
setDeafen: (param: boolean) => void,
|
||||||
leave: () => void
|
leave: () => void,
|
||||||
|
closeCamera: () => void,
|
||||||
|
openCamera: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const VoiceManagement = ({ info, setMute, setDeafen, leave }: Props) => {
|
const VoiceManagement = ({ info, setMute, setDeafen, leave, closeCamera, openCamera }: Props) => {
|
||||||
const { t } = useTranslation("chat");
|
const { t } = useTranslation("chat");
|
||||||
const { userData, voicingMembers } = useAppSelector(store => {
|
const { userData, voicingMembers } = useAppSelector(store => {
|
||||||
return {
|
return {
|
||||||
@@ -33,7 +35,7 @@ const VoiceManagement = ({ info, setMute, setDeafen, leave }: Props) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
if (!info) return null;
|
if (!info) return null;
|
||||||
const { deafen, muted } = 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`);
|
||||||
const members = voicingMembers.ids;
|
const members = voicingMembers.ids;
|
||||||
const membersData = voicingMembers.byId;
|
const membersData = voicingMembers.byId;
|
||||||
@@ -81,7 +83,7 @@ const VoiceManagement = ({ info, setMute, setDeafen, leave }: Props) => {
|
|||||||
{muted ? <IconMicOff className="w-4 fill-gray-500 dark:fill-gray-400" /> : <IconMic className="w-4 fill-gray-500 dark:fill-gray-400" />}
|
{muted ? <IconMicOff className="w-4 fill-gray-500 dark:fill-gray-400" /> : <IconMic className="w-4 fill-gray-500 dark:fill-gray-400" />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id={`CAMERA_${uid}`} className="w-[98%] h-[120px] rounded overflow-hidden m-auto">
|
<div id={`CAMERA_${uid}`} className="w-[98%] rounded overflow-hidden m-auto">
|
||||||
{/* camera placeholder */}
|
{/* camera placeholder */}
|
||||||
</div>
|
</div>
|
||||||
</li>;
|
</li>;
|
||||||
@@ -98,13 +100,11 @@ const VoiceManagement = ({ info, setMute, setDeafen, leave }: Props) => {
|
|||||||
<Tooltip tip={muted ? t("unmute") : t("mute")} placement="top">
|
<Tooltip tip={muted ? t("unmute") : t("mute")} placement="top">
|
||||||
<li role={"button"} onClick={setMute.bind(null, !muted)} className="py-2 px-3 rounded bg-gray-100 dark:bg-gray-900">
|
<li role={"button"} onClick={setMute.bind(null, !muted)} className="py-2 px-3 rounded bg-gray-100 dark:bg-gray-900">
|
||||||
{muted ? <IconMicOff className="fill-gray-700 dark:fill-gray-300" /> : <IconMic className="fill-gray-700 dark:fill-gray-300" />}
|
{muted ? <IconMicOff className="fill-gray-700 dark:fill-gray-300" /> : <IconMic className="fill-gray-700 dark:fill-gray-300" />}
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip tip={"Camera"} placement="top">
|
<Tooltip tip={video ? t("camera_off") : t("camera_on")} placement="top">
|
||||||
<li role={"button"} className="py-2 px-3 rounded bg-gray-100 dark:bg-gray-900">
|
<li role={"button"} onClick={video ? closeCamera : openCamera} 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" />}
|
{video ? <IconCamera className="fill-gray-700 dark:fill-gray-300" /> : <IconCameraOff className="fill-gray-700 dark:fill-gray-300" />}
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip tip={"Share Screen"} placement="top">
|
<Tooltip tip={"Share Screen"} placement="top">
|
||||||
|
|||||||
Vendored
+5
-2
@@ -1,4 +1,4 @@
|
|||||||
import { IAgoraRTCClient, IMicrophoneAudioTrack, IRemoteAudioTrack } from "agora-rtc-sdk-ng";
|
import { IAgoraRTCClient, ICameraVideoTrack, IMicrophoneAudioTrack, IRemoteAudioTrack, IRemoteVideoTrack } from "agora-rtc-sdk-ng";
|
||||||
|
|
||||||
interface BeforeInstallPromptEvent extends Event {
|
interface BeforeInstallPromptEvent extends Event {
|
||||||
/**
|
/**
|
||||||
@@ -34,7 +34,10 @@ export declare global {
|
|||||||
VOICE_CLIENT?: IAgoraRTCClient;
|
VOICE_CLIENT?: IAgoraRTCClient;
|
||||||
VOICE_TRACK_MAP: {
|
VOICE_TRACK_MAP: {
|
||||||
[key: number]: IRemoteAudioTrack | IMicrophoneAudioTrack | undefined | null
|
[key: number]: IRemoteAudioTrack | IMicrophoneAudioTrack | undefined | null
|
||||||
}
|
},
|
||||||
|
VIDEO_TRACK_MAP: {
|
||||||
|
[key: number]: IRemoteVideoTrack | ICameraVideoTrack | undefined | null
|
||||||
|
},
|
||||||
}
|
}
|
||||||
interface WindowEventMap {
|
interface WindowEventMap {
|
||||||
beforeinstallprompt: BeforeInstallPromptEvent;
|
beforeinstallprompt: BeforeInstallPromptEvent;
|
||||||
|
|||||||
Reference in New Issue
Block a user