WGShare.Client.Electron/src/page/Meeting/index.tsx

3539 lines
144 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import styles from '@/page/Meeting/index.module.scss'
import { useEffect, useRef, useState } from "react";
import Operation from '@/components/Operation';
import SpeakerModeModal from '@/components/SpeakerModeModal';
import InvitingPersonnelModal from '@/components/InvitingPersonnelModal';
import { Button, Input, Popover, Modal, Checkbox, message, Popconfirm, notification } from "antd";
import { SearchOutlined, EllipsisOutlined, ExclamationCircleFilled, FullscreenExitOutlined, FullscreenOutlined, QuestionCircleOutlined, CaretLeftOutlined, CaretRightOutlined, CaretUpOutlined, CaretDownOutlined } from '@ant-design/icons';
import { useLocation, useNavigate } from 'react-router-dom';
import { thumbImageBufferToBase64 } from '@/utils/package/base64'
import { storage } from '@/utils';
import { GetRoomUser, PostOpenMicr, GetSharedScreen, PostOpenCamera, GetLeaveAll, PostRoomManager, DeleteRoomManager, GetRoomKickout, GetShowUser, PostShowUser, PostMuteAll, GetRoomUserItem, GetApplySpeak, PostSharedScreen } from '@/api/Meeting';
import ImageUrl from '@/utils/package/imageUrl'
import { agora } from '@/utils/package/agora'
import dayjs from 'dayjs';
import durationPlugin from 'dayjs/plugin/duration';
import { AudioVolumeInfo, ConnectionChangedReasonType, ConnectionStateType, LocalVideoStreamReason, LocalVideoStreamState, RenderModeType, RtcConnection, RtcStats, UserOfflineReasonType, VideoSourceType, VideoStreamType } from 'agora-electron-sdk';
import Avatar from '@/components/Avatar';
import SharedFilesModel from '@/components/SharedFilesModel';
import StupWizard from '@/components/StupWizard';
import EquipmentManagement from '@/components/EquipmentManagement';
import UserVideo from '@/components/UserVideo';
import { role } from '@/config/role';
import { fixWebmDuration } from "webm-duration-fix-buffer";
import { getKeyOpenChildWindow, setKeyOpenChildWindow } from '@/utils/package/public';
import MeetingDisconnected from '@/components/MeetingDisconnected';
import SingIn from '@/components/SingIn';
import UserName from '@/components/UserName';
import { GetRoomRtcToken } from '@/api/Home/Index';
const { setTimeout, setInterval, clearTimeout, clearInterval } = require('timers');
const { confirm } = Modal;
const { exec } = require('child_process');
const fs = require('fs').promises;
dayjs.extend(durationPlugin);
const Meeting: React.FC = () => {
const navigate = useNavigate();
const { state } = useLocation();
const speakerModeModalRef = useRef<any>();
const sharedFilesModelRef = useRef<any>();
const invitingPersonnelRef = useRef<any>();
const stupWizardRef = useRef<any>();
const equipmentManagementRef = useRef<any>();
const meetingDisconnectedRef = useRef<any>();
const singInRef = useRef<any>();
const userNameRef = useRef<any>();
const [isClicked, setIsClicked] = useState(false);
const [isClickedMediaSteam, setIsClickedMediaSteam] = useState(false);
const [statusList, setStatusList] = useState({
userList: false,
userChatList: false,
userVideo: false,
})
const [isSharedScreenModal, setIsSharedScreenModal] = useState(false);
const [quitMeetingModal, setQuitMeetingModal] = useState(false);
const [user, setUser] = useState<any>({});
const [sharedScreenList, setSharedScreenList] = useState<any>([]);
const [sharedScreenItem, setSharedScreenItem] = useState<any>('');
const [textMsg, setTextMsg] = useState('');
const [footerList, setFooterList] = useState<any>([
[
{
title: '解除静音',
icon: ImageUrl.icon22,
iconActive: ImageUrl.icon22Active,
active: true,
select: false,
},
{
title: '开启视频',
icon: ImageUrl.icon23,
iconActive: ImageUrl.icon23Active,
active: true,
select: false,
},
{
title: '申请发言',
icon: ImageUrl.icon47,
iconActive: ImageUrl.icon47Active,
active: false,
select: false,
},
],
[
{
title: '共享屏幕',
icon: ImageUrl.icon24,
iconSelect: ImageUrl.icon24Select,
active: false,
select: false,
},
{
title: '共享文件',
icon: ImageUrl.icon25,
iconSelect: ImageUrl.icon25Select,
active: false,
select: false,
},
{
title: '邀请人员',
icon: ImageUrl.icon26,
iconSelect: ImageUrl.icon26Select,
active: false,
select: false,
},
{
title: '录制',
icon: ImageUrl.icon27,
iconSelect: ImageUrl.icon27Select,
iconActive: ImageUrl.icon27Active,
active: false,
select: false,
},
{
title: '签到',
icon: ImageUrl.icon52,
iconSelect: ImageUrl.icon52Select,
active: false,
select: false,
},
{
title: '设置',
icon: ImageUrl.icon28,
iconSelect: ImageUrl.icon28Select,
active: false,
select: false,
},
{
title: '结束',
icon: ImageUrl.icon29,
iconSelect: ImageUrl.icon29Select,
active: false,
select: false,
},
],
[{
title: '会议监控',
icon: ImageUrl.icon48,
iconSelect: ImageUrl.icon48Select,
active: false,
select: false,
},
{
title: '成员列表',
icon: ImageUrl.icon30,
iconSelect: ImageUrl.icon30Select,
active: false,
select: false,
},
{
title: '聊天',
icon: ImageUrl.icon31,
iconSelect: ImageUrl.icon31Select,
active: false,
select: false,
},
],
])
const [footerListIndex, setFooterListIndex] = useState<any>({
itemIndex: 0,
rowIndex: 0,
});
const [roomUserList, setRoomUserList] = useState<any>([])
const [_speackUid, setSpeackUid] = useState<any>([])
const [currentSpeakUser, setCurrentSpeakUser] = useState<any>([])
const [chatList, setChatList] = useState<any>([])
const [isExpand, setIsExpand] = useState(false)
const [currentVideoId, setCurrentVideoId] = useState('')
const [currentVideoUid, setCurrentVideoUid] = useState('')
let [currentSeconds, setCurrentSeconds] = useState(0)
const [isNetworkQuality, setIsNetworkQuality] = useState(false)
const [currentEffective, setCurrentEffective] = useState(3)
const [networkQuality, setNetworkQuality] = useState({
level: '佳',
text: '网络质量极好'
})
const [networkOther, setNetworkOther] = useState<RtcStats>({})
const [isComputerAudio, setIsComputerAudio] = useState(false)
const [_isLeave, setIsLeave] = useState(false)
const [isScreenCapture, setIsScreenCapture] = useState(false)
const [isFluencyPriority, setIsFluencyPriority] = useState(false)
const [isClickLock, setIsClickLock] = useState(false)
const [open, setOpen] = useState(false)
const [modeOpen, setModeOpen] = useState(false)
const [meetingMode, setMeetingMode] = useState('')
const [userSearchValue, setUserSearchValue] = useState('')
const [noViewChatList, setNoViewChatList] = useState(0)
const [currentLookUserAccount, setCurrentLookUserAccount] = useState<any>('')
const [recorder, setRecorder] = useState<any>('')
const [contextMenu, setContextMenu] = useState<any>('')
const [contextMenuStyle, setContextMenuStyle] = useState({
top: 0,
left: 0,
})
const [_currentRequestSpeakType, setCurrentRequestSpeakType] = useState<'video' | 'audio' | ''>('')
const [_mediaStream, setMediaStream] = useState<any>([])
const [isShare, setIsShare] = useState<any>(null)
const [isSharePopConfirm, setIsSharePopConfirm] = useState(false)
const [_isNetworkDisconnected, setIsNetworkDisconnected] = useState(false)
const [isAgoraDisconnected, setIsAgoraDisconnected] = useState(false)
const [isShareUser, setIsShareUser] = useState<any>(null)
const [currentLookUserStatus, setCurrentLookUserStatus] = useState<0 | 1 | 2 | 3 | 4>(1)
const [commonlyChatList] = useState<any>([
'能听到我说话吗?',
'听得到',
'听不到',
'我要发言',
])
const [roomUserItem, setRoomUserItem] = useState<any>(null)
const [isAdmin, setIsAdmin] = useState<number>(0)
const [_socketRemberNumber, setSocketRemberNumber] = useState<number>(0)
const [api, contextHolder] = notification.useNotification({
stack: {
threshold: 3
}
});
const [isVideoFullScreen, setIsVideoFullScreen] = useState<boolean>(false)
const [observer, setObserver] = useState<IntersectionObserver>()
let userInfo = JSON.parse(storage.getItem('user') as string)
const msgTips = '您不是管理员或发言人,无法开启此功能!'
const channel = new BroadcastChannel('meeting_channel');
let storeDevice: any;
let agoraTime: NodeJS.Timeout;
useEffect(() => {
window.electron.createChildWindow('hide')
let time: NodeJS.Timeout;
let refreshTime: NodeJS.Timeout;
setUser(userInfo)
window.electron.getIsMaximized().then((res: boolean) => {
if (!res) {
window.electron.setViewStatus('maximize')
}
})
setKeyOpenChildWindow('shareScreenWindow', false)
setMeetingMode('StandardMode');
agoraInit()
storage.setItem('noViewChatList', 0)
window.addEventListener('customStorageChange', handleCustomStorageChange);
const container = document.getElementById('videoView') as HTMLElement;
container.addEventListener('wheel', handleWheelChange);
channel.onmessage = async function (event) {
const {
type,
shareScreenWindowfooterListsTitle,
shareScreenWindowClose,
userListWindowPostOpenMicr,
userListWindowPostOpenCamera,
userListWindowDeleteRoomManager,
userListWindowPostRoomManager,
userListWindowGetRoomKickout,
userListWindowEquipmentManagement,
userListWindowSetEquipmentManagement,
chatSmallWindowSendChannelMsg,
chatBigWindowSetAllUserLook,
chatBigWindowDeleteRoomManager,
chatBigWindowPostRoomManager,
chatBigWindowPostOpenMicr,
chatBigWindowPostOpenCamera,
chatBigWindowGetRoomKickout,
chatBigWindowSendChannelMsg,
noticeWindowPostRoomManager
} = event.data;
switch (type) {
case 'shareScreenWindowGetTime':
setCurrentSeconds((res => {
channel.postMessage({
type: 'time',
time: res,
});
return res
}))
break;
case 'shareScreenWindowClose':
setCurrentSeconds(shareScreenWindowClose)
await stopScreenCapture()
await allUserLook(userInfo.uid, userInfo.userName)
break;
case 'shareScreenWindowfooterListsTitle':
switch (shareScreenWindowfooterListsTitle) {
case '静音':
case '解除静音':
changeStatusList({
title: shareScreenWindowfooterListsTitle
}, 0, 1)
break;
case '关闭视频':
case '开启视频':
changeStatusList({
title: shareScreenWindowfooterListsTitle
}, 0, 2)
break;
case '录制':
case '录制中':
changeStatusList({
title: shareScreenWindowfooterListsTitle
}, 1, 3)
break;
}
break;
case 'shareScreenWindowGetFooterLists':
setFooterList((res: any) => {
window.electron.windowHandleMessage({
key: 'shareScreenWindow',
parmes: {
footerList: res,
type: 'footerList'
}
})
return res
})
break;
case 'userListWindowEquipmentManagement':
await window.electron.onInvoke('getDrivers', {
uid: userListWindowEquipmentManagement.uid,
})
break;
case 'userListWindowSetEquipmentManagement':
await window.electron.onInvoke('setDrivers', {
uid: userListWindowSetEquipmentManagement.uid,
driversJsonString: userListWindowSetEquipmentManagement.driversJsonString
})
break;
case 'userListWindowPostOpenMicr':
postOpenMicr(userListWindowPostOpenMicr.enableMicr, userListWindowPostOpenMicr.uid)
break;
case 'userListWindowPostOpenCamera':
postOpenCamera(userListWindowPostOpenCamera.enableCamera, userListWindowPostOpenCamera.uid)
break;
case 'userListWindowDeleteRoomManager':
DeleteRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: userListWindowDeleteRoomManager.uid
})
break;
case 'userListWindowPostRoomManager':
postRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: userListWindowPostRoomManager.uid
})
break;
case 'userListWindowGetRoomUserList':
setRoomUserList(((res: any) => {
window.electron.windowHandleMessage({
key: 'userListWindow',
parmes: {
roomUserList: res,
type: 'roomUserList'
}
})
return res
}))
break;
case 'userListWindowGetRoomKickout':
GetRoomKickout(state.channelId, userListWindowGetRoomKickout.uid)
break;
case 'userListWindowAllPostOpenMicr':
postOpenMicr(false, userInfo.id, true)
break;
case 'chatSmallWindowSendChannelMsg':
sendMsg(chatSmallWindowSendChannelMsg.msg)
break;
case 'chatBigWindowSetAllUserLook':
setAllUserLook(chatBigWindowSetAllUserLook.roomUserItem)
break;
case 'chatBigWindowDeleteRoomManager':
DeleteRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: chatBigWindowDeleteRoomManager.uid
})
break;
case 'chatBigWindowPostRoomManager':
postRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: chatBigWindowPostRoomManager.uid
})
break;
case 'chatBigWindowPostOpenMicr':
postOpenMicr(chatBigWindowPostOpenMicr.enableMicr, chatBigWindowPostOpenMicr.uid)
break;
case 'chatBigWindowPostOpenCamera':
postOpenCamera(chatBigWindowPostOpenCamera.enableCamera, chatBigWindowPostOpenCamera.uid)
break;
case 'chatBigWindowGetRoomKickout':
GetRoomKickout(state.channelId, chatBigWindowGetRoomKickout.uid)
break;
case 'chatBigWindowSendChannelMsg':
if (chatBigWindowSendChannelMsg.msg) {
sendMsg(chatBigWindowSendChannelMsg.msg)
} else[
setChatList((res: any) => {
window.electron.windowHandleMessage({
key: 'chatBigWindow',
parmes: {
chatList: res,
}
})
return res
})
]
break;
case 'noticeWindowPostRoomManager':
postRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: noticeWindowPostRoomManager.uid
})
break;
}
}
time = setInterval(() => {
setCurrentSeconds(currentSeconds => {
return currentSeconds += 1
})
setSpeackUid((uids: any) => {
const usernames: string[] = [];
setRoomUserList((res: any) => {
uids.forEach((uid: any) => {
const user = res.find((item: any) => item.uid == uid);
if (user) {
usernames.push(user.userName);
}
})
setCurrentSpeakUser(usernames)
return res
});
return []
})
}, 1000)
refreshTime = setInterval(() => {
GetRoomUser(state.channelId).then(res => {
if (res.code === 200) {
res.data.forEach((item: any) => {
setRoomUserList((data: any) => {
let row = data.find((row: any) => row.uid == item.uid);
if (row) {
row.enableCamera = item.enableCamera;
row.enableMicr = item.enableMicr;
row.isRoomManager = item.isRoomManager;
row.isAdmin = role.ID.includes(item.roleId) || item.isRoomManager;
}
return data
})
});
}
})
}, 1000 * 30)
// 首次加载图标更新
const firstFooterList = [...footerList]
firstFooterList[0][0].title = state.enableMicr ? '静音' : '解除静音'
firstFooterList[0][0].active = !state.enableMicr
firstFooterList[0][1].title = state.enableCamera ? '关闭视频' : '开启视频'
firstFooterList[0][1].active = !state.enableCamera
setFooterList(firstFooterList)
function showSingIn() {
if (!role.ID.includes(userInfo.roleId)) {
singInRef.current.getModal().then((res: boolean) => {
if (!res) {
singInRef.current.changeModal()
}
})
}
}
setTimeout(async () => {
const setting = await JSON.parse(storage.getItem('setting') as string);
const stateInfo = await JSON.parse(storage.getItem('stateInfo') as string);
if (stateInfo && setting.isRecordingTips && location.href.indexOf('/meeting') !== -1) {
setRecorder((data: any) => {
if (!data) {
setIsScreenCapture(bool => {
if (!bool) {
if (role.ID.includes(userInfo.roleId)) {
confirm({
title: '提示',
icon: <ExclamationCircleFilled />,
content: `是否录制本次会议?`,
centered: true,
okText: '确定',
cancelText: '取消',
async onOk() {
if (stateInfo) {
changeStatusList({
title: '录制'
}, 1, 3)
} else {
message.error('当前不在会议室!')
}
showSingIn()
},
onCancel() {
showSingIn()
}
})
} else {
showSingIn()
}
}
return bool
})
}
return data
})
} else {
setIsScreenCapture(bool => {
if (!bool) {
showSingIn()
}
return bool
})
}
}, 10000);
return () => {
window.removeEventListener('customStorageChange', handleCustomStorageChange);
window.removeEventListener('wheel', handleWheelChange);
clearInterval(time)
clearInterval(refreshTime)
channel.close();
window.electron.closeChildWindow('shareScreenWindow')
};
}, []);
useEffect(() => {
if (networkOther) {
let data = networkOther as any;
if (storage.getItem('reconnect') === 'true') {
if (data.lastmileDelay < 100) {
setNetworkQuality({
level: '佳',
text: '网络质量极好'
})
setCurrentEffective(3)
window.electron.windowHandleMessage({
key: 'shareScreenWindow',
parmes: {
currentEffective: 3,
networkQuality: {
level: '佳',
text: '网络质量极好'
},
networkOther: {
lastmileDelay: data.lastmileDelay
},
type: 'nnetworkStatus'
}
})
} else if (data.lastmileDelay > 500) {
setNetworkQuality({
level: '非常差',
text: '完全无法沟通'
})
setIsNetworkQuality(true)
setCurrentEffective(1)
window.electron.windowHandleMessage({
key: 'shareScreenWindow',
parmes: {
currentEffective: 1,
networkQuality: {
level: '非常差',
text: '完全无法沟通'
},
networkOther: {
lastmileDelay: data.lastmileDelay
},
type: 'nnetworkStatus'
}
})
} else if (data.lastmileDelay < 500 && data.lastmileDelay > 100) {
setNetworkQuality({
level: '差',
text: '勉强能沟通但不顺畅'
})
setIsNetworkQuality(true)
setCurrentEffective(2)
window.electron.windowHandleMessage({
key: 'shareScreenWindow',
parmes: {
currentEffective: 2,
networkQuality: {
level: '差',
text: '勉强能沟通但不顺畅'
},
networkOther: {
lastmileDelay: data.lastmileDelay
},
type: 'nnetworkStatus'
}
})
}
} else {
setNetworkQuality({
level: '断开连接',
text: '网络连接断开'
})
setCurrentEffective(0)
window.electron.windowHandleMessage({
key: 'shareScreenWindow',
parmes: {
currentEffective: 0,
networkQuality: {
level: '断开连接',
text: '网络连接断开'
},
networkOther: {
lastmileDelay: data.lastmileDelay
},
type: 'nnetworkStatus'
}
})
}
}
}, [networkOther]);
useEffect(() => {
if (chatList.length) {
window.electron.windowHandleMessage({
key: 'chatBigWindow',
parmes: {
chatList,
}
})
}
}, [chatList]);
useEffect(() => {
let currentVideoUserItem = roomUserList.find((item: any) => item.uid === currentVideoId || item.screenShareId === currentVideoId)
if (currentVideoUserItem) {
setCurrentLookUserAccount(currentVideoUserItem)
}
}, [currentVideoId]);
useEffect(() => {
if (isShare) {
const item = roomUserList.find((item: any) => item.screenShareId === String(isShare))
setIsShareUser(item || null)
}
}, [isShare, roomUserList]);
useEffect(() => {
roomUserList.forEach(async (item: any) => {
if (item.uid === currentVideoId) {
await agora.setRemoteVideoStreamType(item.uid, VideoStreamType.VideoStreamHigh, true)
} else {
await agora.setRemoteVideoStreamType(item.uid, VideoStreamType.VideoStreamLow, true)
}
});
}, [currentVideoId, roomUserList]);
useEffect(() => {
let item = roomUserList.find((item: any) => currentVideoId == item.uid)
if (item) {
if (isShare) {
if (Number(item.screenShareId) === Number(isShare)) {
agora.muteRemoteVideoStream(Number(isShare), false)
} else {
agora.muteRemoteVideoStream(Number(isShare), true)
}
}
}
}, [currentVideoId, isShare]);
useEffect(() => {
if (isAgoraDisconnected) {
agoraTime = setTimeout(() => {
confirm({
keyboard: false,
title: '提示',
icon: <ExclamationCircleFilled />,
content: `重连失败,请退出房间重试!`,
centered: true,
okText: '退出',
wrapClassName: 'hideCancelText',
cancelText: '',
async onOk() {
leaveChannel()
},
})
clearTimeout(agoraTime)
}, 1000 * 60);
} else {
clearTimeout(agoraTime)
}
return () => clearTimeout(agoraTime);
}, [isAgoraDisconnected]);
useEffect(() => {
window.electron.onSignalr(async (_e: any, item: any) => {
const setting = JSON.parse(storage.getItem('setting') as string)
switch (item.key) {
// 聊天
case 'ReceiveMessage':
let meetingUserChatDom = document.getElementById('meetingUserChat') as HTMLElement;
if (!meetingUserChatDom) {
let storageNoViewChatList = Number(storage.getItem('noViewChatList'))
storage.setItem('noViewChatList', storageNoViewChatList += 1)
setNoViewChatList(storageNoViewChatList)
}
setChatList((newChatList: any) => [...newChatList, item])
window.electron.windowHandleMessage({
key: 'chatSmallWindow',
parmes: {
chatListIten: item,
}
})
setStatusList((res: any) => {
if (!res.userChatList) {
api.open({
message: item.userName + '说:',
description: item.message,
duration: 3,
showProgress: true,
});
}
return res
})
chatScrollBotton()
break;
// 扩展操作
case 'Operation':
switch (item.type) {
}
break;
// 全员离开房间
case 'AllLeave':
message.success('管理员已结束会议!')
leaveChannel(false)
break;
// 移出会议
case 'ForceExitRoom':
message.success('管理员已将你移出会议!')
leaveChannel()
break;
// 更新视图模式
case 'RefreshView':
setMeetingMode(item.type)
break;
// 全员看他
case 'ShowUser':
if (item.operUid && item.operUserName) {
if (item.operUid !== userInfo.uid) {
if (item.uid === userInfo.uid) {
message.success(`${item.operUserName}设置全员看你`)
} else {
message.success(`${item.operUserName}设置全员看${item.uname}`)
}
}
}
getShowUser()
break;
// 用户加入频道回调
case 'UserJoined':
setAllUserListData('UserJoined', item)
break;
// 用户退出频道回调
case 'UserLeave':
setAllUserListData('UserLeave', item)
break;
// 所有用户开闭麦
case 'OperAllMicr':
setAllUserListData('OperAllMicr', item)
break;
// 用户关闭开启麦克风
case 'OperMicr':
if (item.operUid !== userInfo.uid) {
if (item.user.uid === userInfo.uid) {
message.success(item.user.enableMicr ? '管理员已取消你的静音' : '你已被管理员静音')
}
}
setAllUserListData('OperMicr', item)
break;
// 用户开启关闭摄像头
case 'OperCamera':
if (item.operUid !== userInfo.uid) {
if (item.user.uid === userInfo.uid) {
message.success(item.user.enableCamera ? '管理员已开启你的摄像头' : '管理员已关闭你摄像头')
}
}
setAllUserListData('OperCamera', item)
break;
// 发言人用户信息刷新
case 'ManagerRefresh':
setAllUserListData('ManagerRefresh', item, async () => {
if (item.user.uid === item.uid) {
if (item.user.uid === userInfo.uid) {
await agora.allLeaveChannelEx()
message.success(`操作成功`)
await agora.updateChannelMediaOptions(item.user.isRoomManager)
await postOpenMicrApi(item.user.isRoomManager, userInfo.uid, false)
await postOpenCameraApi(false, userInfo.uid) // 不管身份如何改变都关闭摄像头
await stopScreenCapture()
} else {
message.success(`${item.user.userName}已结束发言`)
}
} else {
if (item.user.uid === userInfo.uid) {
if (item.user.isRoomManager) {
await agora.allLeaveChannelEx()
}
message.success(`管理员${item.user.isRoomManager ? '设置' : '取消'}您为发言人`)
await agora.updateChannelMediaOptions(item.user.isRoomManager)
setCurrentRequestSpeakType(res => {
if (res === 'video') {
postOpenCameraApi(item.user.isRoomManager, userInfo.uid)
} else if (res === 'audio') {
postOpenMicrApi(item.user.isRoomManager, userInfo.uid, false)
} else {
postOpenMicrApi(item.user.isRoomManager, userInfo.uid, false)
postOpenCameraApi(false, userInfo.uid)
}
return ''
})
await stopScreenCapture()
} else {
message.success(`管理员${item.user.isRoomManager ? '设置' : '取消'}${item.user.userName}为发言人`)
}
}
})
break;
// 申请发言
case 'ApplyToSpeak':
setIsScreenCapture(bool => {
if (bool) {
window.electron.setChildWindowShow({
key: 'noticeWindow',
bool: true
})
channel.postMessage({
type: 'noticeItem',
noticeItem: item
});
} else {
api.open({
message: '',
description: <div>
<span style={{ fontSize: '16px' }}>{item.uname}</span>
<div style={{ display: 'flex', justifyContent: 'flex-end' }} className='drag'>
<Button
type="primary"
className='m-ant-btn'
onClick={async (e: any) => {
let i = e.nativeEvent.path;
if (i) {
i.forEach((i: any) => {
if (i.className === 'ant-notification-notice ant-notification-notice-closable') {
i.childNodes.forEach((row: any) => {
if (row.className === 'ant-notification-notice-close') {
row.click()
postRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: item.uid
})
}
})
}
})
}
}}
></Button>
<Button
type="primary"
onClick={(e: any) => {
let item = e.nativeEvent.path;
if (item) {
item.forEach((item: any) => {
if (item.className === 'ant-notification-notice ant-notification-notice-closable') {
item.childNodes.forEach((row: any) => {
if (row.className === 'ant-notification-notice-close') {
row.click()
}
})
}
})
}
}}
style={{ backgroundColor: '#EC3C3C', marginLeft: '14px' }}
></Button>
</div>
</div>,
duration: 10,
placement: 'bottomRight',
showProgress: true,
pauseOnHover: false,
});
}
return bool
})
break;
// 管理员查看随机用户
case 'Watch':
if (!role.ID.includes(userInfo.roleId)) {
let userId = item.watchUids.find((uid: any) => uid === userInfo.uid)
if (userId) {
GetRoomRtcToken(state.channelId + 'a').then(async res => {
if (res.code === 200) {
await agora.allJoinChannelEx(false, res.data)
}
})
} else {
await agora.allLeaveChannelEx()
}
}
break;
// 设备列表
case 'DriverList':
Promise.all([
agora.getVideoDeviceManager(),
agora.getAudioMediaList(),
]).then((res) => {
const data = {
videoList: res[0].list.map((row: any) => {
return {
value: row.deviceId,
label: row.deviceName
}
}),
videoDeviceId: res[0].list.find((row: any) => row.deviceId === setting.videoDeviceId) ? setting.videoDeviceId : res[0].list.length ? res[0].list[0].deviceId : null,
ecordingList: res[1].ecordingList.map((row: any) => {
return {
value: row.deviceId,
label: row.deviceName
}
}),
ecordingDeviceId: res[1].ecordingList.find((row: any) => row.deviceId === setting.ecordingDeviceId) ? setting.ecordingDeviceId : res[1].ecordingList.length ? res[1].ecordingList[0].deviceId : null,
playBackList: res[1].playBackList.map((row: any) => {
return {
value: row.deviceId,
label: row.deviceName
}
}),
playBackDeviceId: res[1].playBackList.find((row: any) => row.deviceId === setting.playBackDeviceId) ? setting.playBackDeviceId : res[1].playBackList.length ? res[1].playBackList[0].deviceId : null,
ecordingVolume: res[1].ecordingVolume
}
window.electron.onInvoke('sendDrivers', {
uid: item.callerUid,
driversJsonString: JSON.stringify(data)
})
})
break;
// 设置设备
case 'SaveDriver':
if (item.driver) {
const data = JSON.parse(item.driver);
if (data.videoDeviceId) await agora.setVideoDeviceManager(data.videoDeviceId);
if (data.ecordingDeviceId) await agora.setRecordingDevice(data.ecordingDeviceId)
if (data.playBackDeviceId) await agora.setPlaybackDevice(data.playBackDeviceId)
await agora.setRecordingDeviceVolume(data.ecordingVolume)
setting.videoDeviceId = data.videoDeviceId || null;
setting.ecordingDeviceId = data.ecordingDeviceId || null;
setting.playBackDeviceId = data.playBackDeviceId || null;
setting.ecordingVolume = data.ecordingVolume
storage.setItem('setting', JSON.stringify(setting))
}
break;
// 显示设备列表
case 'ShowDriverList':
if (item.driversJsonString) {
const data = JSON.parse(item.driversJsonString);
const isOpen = await getKeyOpenChildWindow('shareScreenWindow')
if (isOpen) {
channel.postMessage({ type: 'showDriverList', showDriverList: data })
} else {
equipmentManagementRef.current.setData(data)
}
}
break;
// 修改用户名称
case 'ModifyNickName':
setAllUserListData('ModifyNickName', item)
break;
// 加入房间回调
case 'JoinChannelCallback':
if (item.isSuccess) {
await getRoomUser()
} else {
setSocketRemberNumber(res => {
if (res >= 3) {
confirm({
keyboard: false,
title: '提示',
icon: <ExclamationCircleFilled />,
content: `加入房间失败!`,
centered: true,
okText: '退出',
wrapClassName: 'hideCancelText',
cancelText: '',
async onOk() {
leaveChannel()
},
})
} else {
setTimeout(() => {
async function setUserStatus(res: any) {
let userItem = res.find((item: any) => item.uid === userInfo.uid)
await window.electron.onInvoke('joinChannel', {
roomNum: state.channelId,
enableMicr: !storeDevice[0][0].active,
enableCamera: !storeDevice[0][1].active,
isRoomManager: userItem ? userItem.isRoomManager : false,
})
}
setRoomUserList((res: any) => {
setUserStatus(res)
return res
})
}, 3000);
}
return res >= 3 ? 3 : res++
})
}
break;
// 共享
case 'ExitSharedScreen':
setIsScreenCapture((res) => {
if (res) {
changeStatusList({
title: '共享冲突停止共享',
}, 1, 0)
}
return res
})
break;
}
})
return () => {
window.electron.offSignalr()
}
}, [])
useEffect(() => {
if (recorder) {
recorder.start()
recorder.onstart = async () => {
message.success('开始录制')
}
recorder.onerror = async () => {
setRecorder('')
setMediaStream([])
setIsClickedMediaSteam(false)
changeStatusList({
title: '录制中'
}, 1, 3)
message.error('录制失败,请重新录制!')
}
recorder.onstop = async () => {
}
recorder.ondataavailable = async (event: any) => {
const blob = await fixWebmDuration(event.data);
const reader = new FileReader() as any;
reader.onload = async () => {
const setting = await JSON.parse(storage.getItem('setting') as string)
const buffer = Buffer.from(reader.result);
const mp4Path = `${setting.recordingFilesPath}会议录制_${state.roomName}_${state.channelId}_${dayjs().format('YYYY年MM月DD日HH时mm分')}.webm`;
await fs.writeFile(mp4Path, buffer, {});
setRecorder('')
setMediaStream([])
confirm({
title: '提示',
icon: <ExclamationCircleFilled />,
content: `录制成功!文件已保存至:${setting.recordingFilesPath}`,
centered: true,
okText: '打开文件夹',
cancelText: '关闭',
async onOk() {
await fs.access(setting.recordingFilesPath, fs.constants.F_OK);
if (process.platform === 'win32') {
exec(`explorer "${setting.recordingFilesPath}"`);
} else if (process.platform === 'darwin') {
exec(`open "${setting.recordingFilesPath}"`);
}
},
onCancel() {
}
})
setIsLeave(bool => {
if (bool) {
if (userInfo.isAnonymous) {
storage.setItem('userLogin', false)
} else {
navigate('/home/index')
}
}
return false
})
};
reader.readAsArrayBuffer(blob);
}
};
}, [recorder])
useEffect(() => {
let timer: NodeJS.Timeout;
if (isClicked) {
timer = setTimeout(() => {
setIsClicked(false);
setCurrentRequestSpeakType('')
}, 10000);
}
return () => clearTimeout(timer);
}, [isClicked]);
useEffect(() => {
async function setView() {
let uid = currentVideoUid
switch (currentLookUserStatus) {
case 1:
await agora.setupLocalVideo({
uid: Number(uid),
view: document.getElementById(`video-source-camera-primary`) as HTMLElement,
channelId: state.channelId,
sourceType: VideoSourceType.VideoSourceCameraPrimary,
renderMode: RenderModeType.RenderModeFit
})
setIsClickLock(false)
break;
case 2:
await agora.setupLocalVideo({
uid: Number(uid),
view: document.getElementById(`video-source-screen`) as HTMLElement,
channelId: state.channelId,
sourceType: VideoSourceType.VideoSourceScreen,
renderMode: RenderModeType.RenderModeFit
})
setIsClickLock(false)
break;
case 3:
await agora.setupRemoteVideoJoin({
uid: Number(uid),
view: document.getElementById(`video-source-remote-screen`) as HTMLElement,
channelId: state.channelId,
renderMode: RenderModeType.RenderModeFit
})
setIsClickLock(false)
break;
case 4:
await agora.setupRemoteVideoJoin({
uid: Number(uid),
view: document.getElementById(`video-source-remote-camera`) as HTMLElement,
channelId: state.channelId,
renderMode: RenderModeType.RenderModeFit
})
setIsClickLock(false)
break;
}
}
if (currentVideoUid) {
setTimeout(() => {
setView()
}, currentVideoId ? 0 : 1500);
}
}, [currentVideoUid]);
useEffect(() => {
if (isScreenCapture) {
agora.setupLocalVideo({
uid: Number(user.uid),
view: document.getElementById(`meetingAbsoluteVideo`) as HTMLElement,
channelId: state.channelId,
sourceType: VideoSourceType.VideoSourceCameraPrimary,
type: true
})
}
}, [isScreenCapture]);
useEffect(() => {
let timer: NodeJS.Timeout;
if (isClickedMediaSteam) {
timer = setTimeout(() => {
setIsClickedMediaSteam(false)
}, 3000);
}
return () => clearTimeout(timer);
}, [isClickedMediaSteam]);
useEffect(() => {
const elements = document.querySelectorAll('.intersectionObserver-view');
if (elements.length && currentVideoId) {
elements.forEach(element => {
observer?.unobserve(element);
});
const observerObject = new IntersectionObserver(async (entries: IntersectionObserverEntry[], _observer: IntersectionObserver) => {
setIsScreenCapture((bool: boolean) => {
entries.forEach(async (entry) => {
if (entry.target.id !== user.uid) {
await agora.muteRemoteVideoStreamEx(Number(entry.target.id), bool ? true : !entry.isIntersecting)
}
});
return bool
})
setIsScreenCapture((bool: boolean) => {
agora.muteRemoteVideoStreamEx(Number(currentVideoId), bool)
return bool
})
}, { threshold: 0, root: document.getElementById('videoView') });
setObserver(observerObject)
elements.forEach(element => {
observerObject.observe(element);
});
}
window.electron.windowHandleMessage({
key: 'shareScreenWindow',
parmes: {
footerList,
type: 'footerList'
}
})
window.electron.windowHandleMessage({
key: 'shareScreenWindow',
parmes: {
roomUserList,
type: 'roomUserList'
}
})
window.electron.windowHandleMessage({
key: 'userListWindow',
parmes: {
roomUserList,
type: 'roomUserList'
}
})
return () => {
elements.forEach(element => {
observer?.unobserve(element);
});
observer?.disconnect();
}
}, [roomUserList, currentVideoId, footerList]);
// 声网初始化
const agoraInit = async () => {
await agora.init(true)
await agora.muteLocalVideoStream(userInfo, state.enableMicr, state.enableCamera)
await getJoin(state.enableMicr, state.enableCamera)
agora.registerEventHandler({
onJoinChannelSuccess: async (connection: RtcConnection, _elapsed: number) => {
if (connection.channelId === state.channelId) {
if (String(connection.localUid).length !== 9) {
setTimeout(async () => {
await agora.setupLocalVideo({
uid: Number(connection.localUid),
view: document.getElementById(`video-${connection.localUid}`),
channelId: connection.channelId,
sourceType: VideoSourceType.VideoSourceCameraPrimary,
renderMode: RenderModeType.RenderModeFit
})
}, 1000);
getShowUser();
}
}
},
onUserJoined: async (connection: RtcConnection, remoteUid: number, _elapsed: number) => {
if (connection.channelId === state.channelId) {
if (String(remoteUid).length === 9) {
setIsShare(remoteUid)
} else {
setTimeout(async () => {
await agora.setupRemoteVideoJoin({
uid: Number(remoteUid),
view: document.getElementById(`video-${remoteUid}`),
channelId: connection.channelId,
renderMode: RenderModeType.RenderModeFit
})
}, 1000);
}
}
},
onUserOffline: async (connection: RtcConnection, remoteUid: number, _reason: UserOfflineReasonType) => {
await agora.destroyRendererByConfig(Number(remoteUid), connection.channelId)
if (connection.channelId === state.channelId) {
if (String(remoteUid).length === 9) {
setIsShare(null)
}
await agora.setupRemoteVideo({
uid: Number(remoteUid),
view: null,
channelId: connection.channelId,
renderMode: RenderModeType.RenderModeFit
});
setCurrentVideoUid(res => {
if (String(remoteUid) === res) {
getShowUser();
}
return res
})
}
},
onAudioVolumeIndication: async (speakers: AudioVolumeInfo[]) => {
if (speakers.length) {
setSpeackUid((res: any) => {
return [...new Set([...res, ...(speakers.filter((item: any) => item.volume)).map(item => item.uid || userInfo.uid)])]
})
speakers.forEach((item: any) => {
let domMe = document.getElementById(`micr-item-${userInfo.uid}`);
let dom = document.getElementById(`micr-${item.uid ? item.uid : userInfo.uid}`);
if (dom) {
const percentage = (item.volume / 255) * 100
dom.style.height = `${percentage}%`
}
if (!item.uid) {
const percentage = (item.volume / 255) * 100
if (domMe) {
domMe.style.height = `${percentage}%`
}
window.electron.windowHandleMessage({
key: 'shareScreenWindow',
parmes: {
currentSpeakUserMe: percentage,
type: 'currentSpeakUserMe'
}
})
}
});
}
},
onRtcStats: async (stats: RtcStats) => {
setNetworkOther(stats)
},
onConnectionStateChanged: async (_connection: RtcConnection, state: ConnectionStateType, reason: ConnectionChangedReasonType) => {
const reconnectingCode = [2, 16, 11, 13, 14, 12]
if (state === 4 && reconnectingCode.indexOf(reason) >= 0) {
} else if (state === 3) {
meetingDisconnectedRef.current.changeModal(false)
setIsAgoraDisconnected(false)
}
},
onConnectionLost: () => {
meetingDisconnectedRef.current.changeModal(true)
setIsAgoraDisconnected(true)
setIsScreenCapture(bool => {
if (bool) {
setIsNetworkDisconnected(true)
stopScreenCapture()
}
return bool
})
},
onLocalVideoStateChanged: async (_source: VideoSourceType, _state: LocalVideoStreamState, reason: LocalVideoStreamReason) => {
if (reason === 12) {
setIsScreenCapture(bool => {
if (bool) {
stopScreenCapture()
setSharedScreenItem('')
allUserLook(userInfo.uid, userInfo.userName)
}
return bool
})
} else if (reason === 3 || reason === 4) {
message.error({
content: <div><span style={{ color: '#606fc7', cursor: 'pointer' }} onClick={() => {
stupWizardRef.current.changeModal(1);
}}></span></div>,
duration: 60,
key: 'cameraTemporarily'
});
}
},
onTokenPrivilegeWillExpire: async (connection: RtcConnection, _token: string) => {
await GetRoomRtcToken(connection.channelId || '').then(res => {
if (res.code === 200) {
agora.refreshToken({
token: res.data,
connection,
})
}
})
}
})
if (state.enableCamera) {
await agora.startCameraCapture()
}
await agora.setJoinChannel({
channelId: state.channelId,
uid: userInfo.uid,
screenShareId: userInfo.screenShareId,
token: state.token,
})
}
// 状态更新
const changeAgoraDevice = () => {
setRoomUserList((res: any) => {
res.forEach(async (item: any) => {
if (item.uid === userInfo.uid) {
const footerListTemplate = [...footerList]
await agora.getVideoDeviceManager().then(async (res) => {
getUserRoomInfo().then(async (r) => {
if (res.list.length) {
footerListTemplate[0][1].title = item.enableCamera ? '关闭视频' : '开启视频'
footerListTemplate[0][1].active = !item.enableCamera
await agora.muteLocalVideoStream(r, item.enableMicr, item.enableCamera)
} else {
footerListTemplate[0][1].title = '开启视频'
footerListTemplate[0][1].active = true
await agora.muteLocalVideoStream(r, item.enableMicr, false)
}
})
})
await agora.getAudioMediaList().then(async (res) => {
getUserRoomInfo().then(async (r) => {
if (res.ecordingList.length) {
footerListTemplate[0][0].title = item.enableMicr ? '静音' : '解除静音'
footerListTemplate[0][0].active = !item.enableMicr
await agora.muteLocalAudioStream(r, item.enableMicr, item.enableCamera)
} else {
footerListTemplate[0][0].title = '解除静音'
footerListTemplate[0][0].active = true
await agora.muteLocalAudioStream(r, false, item.enableCamera)
}
})
})
if (!role.ID.includes(userInfo.roleId)) {
if (item.isRoomManager) {
footerListTemplate[0][2].title = '结束发言'
footerListTemplate[0][2].active = true
} else {
footerListTemplate[0][2].title = '申请发言'
footerListTemplate[0][2].active = false
}
}
setFooterList(footerListTemplate)
}
if (userSearchValue) {
if (item.userName.indexOf(userSearchValue) !== -1) {
item.isShow = true;
} else {
item.isShow = false;
}
} else {
item.isShow = true;
}
});
setIsAdmin(res.filter((item: any) => (role.ID.includes(item.roleId) || item.isRoomManager) && item.isRoom).length)
return res
})
}
// 刷新
const refreshVideoView = async (userItem: any): Promise<void> => {
if (userItem.uid === userInfo.uid) {
await agora.setupLocalVideo({
uid: Number(userItem.uid),
view: document.getElementById(`video-${userItem.uid}`),
channelId: state.channelId,
sourceType: VideoSourceType.VideoSourceCameraPrimary,
renderMode: RenderModeType.RenderModeFit
})
} else {
await agora.setupRemoteVideoJoin({
uid: Number(userItem.uid),
view: document.getElementById(`video-${userItem.uid}`),
channelId: state.channelId,
renderMode: RenderModeType.RenderModeFit
})
}
}
// 替换数据
const setAllUserListData = async (key: string, item: any, callBack?: Function): Promise<void> => {
switch (key) {
case 'OperMicr':
case 'OperCamera':
case 'ManagerRefresh':
setRoomUserList((res: any) => {
let userItem = res.find((row: any) => row.uid === item.user.uid)
if (userItem) {
for (const keys in item.user) {
if (keys !== 'enableCamera' && keys !== 'enableMicr') {
userItem[keys] = item.user[keys];
}
}
if (key === 'OperCamera') {
userItem.enableCamera = item.user.enableCamera;
if (userItem.uid === userInfo.uid) {
userItem.enableCamera ? agora.startCameraCapture() : agora.stopCameraCapture()
}
}
if (key === 'OperMicr') {
userItem.enableMicr = item.user.enableMicr;
}
userItem.isAdmin = role.ID.includes(item.user.roleId) || item.user.isRoomManager;
if (!userItem.isAdmin) {
agora.destroyRendererByConfig(Number(userItem.uid), state.channelId)
}
refreshVideoView(userItem)
}
if (key === 'ManagerRefresh') {
callBack && callBack()
}
return res
})
break;
case 'UserJoined':
setRoomUserList((res: any) => {
let userItem = res.find((row: any) => row.uid === item.user.uid)
if (userItem) {
for (const key in item.user) {
userItem[key] = item.user[key];
}
userItem.isRoom = true;
userItem.isAdmin = role.ID.includes(item.user.roleId) || item.user.isRoomManager;
setTimeout(() => {
refreshVideoView(userItem)
}, 0);
return [...res]
} else {
item.user.isRoom = true;
item.user.isAdmin = role.ID.includes(item.user.roleId) || item.user.isRoomManager;
refreshVideoView(item.user)
return [...res, item.user]
}
})
break;
case 'UserLeave':
setRoomUserList((res: any) => {
let userItem = res.find((row: any) => row.uid === item.uid)
if (userItem) {
userItem.isRoom = false
userItem.isAdmin = false
}
return res
})
setCurrentVideoId((res: any) => {
if (Number(res) === Number(item.uid)) {
getShowUser();
}
return res
})
break;
case 'OperAllMicr':
setRoomUserList((res: any) => {
res.forEach((row: any) => {
if (row.uid !== item.uid) {
row.enableMicr = item.enableMicr
}
})
return res
})
break;
case 'ModifyNickName':
setRoomUserList((res: any) => {
let userItem = res.find((row: any) => row.uid == item.uid)
if (userItem) {
userItem.userName = item.nickName
}
return res
})
break;
}
changeAgoraDevice()
}
// 滚动
const handleWheelChange = (e: any): void => {
setMeetingMode(res => {
if (res === 'StandardMode') {
const container = document.getElementById('videoView') as HTMLElement;
if (e.wheelDeltaY > 0) {
container.scrollLeft -= 100
} else {
container.scrollLeft += 100
}
}
return res
})
}
// 渲染视频
const renderVideo = async (uid: string = ''): Promise<void> => {
if (isClickLock) {
return
}
if (uid) {
if (currentVideoId === uid || currentVideoUid === uid) {
return
}
} else {
uid = userInfo.uid
}
await agora.destroyRendererByView(`video-source-camera-primary`)
await agora.destroyRendererByView(`video-source-screen`)
await agora.destroyRendererByView(`video-source-remote-screen`)
await agora.destroyRendererByView(`video-source-remote-camera`)
setCurrentLookUserStatus(0)
setRoomUserList((res: any) => {
let item = res.find((item: any) => item.uid === uid || item.screenShareId === uid)
if (item) {
setCurrentVideoId(item.uid)
}
return res
})
setIsClickLock(true)
setCurrentVideoUid('')
if (uid === userInfo.uid || uid === userInfo.screenShareId) {
if (String(uid).length === 9) {
// 共享屏幕
setCurrentLookUserStatus(2)
} else {
// 摄像头
setCurrentLookUserStatus(1)
}
} else {
if (String(uid).length === 9) {
// 共享屏幕
setCurrentLookUserStatus(3)
} else {
// 摄像头
setCurrentLookUserStatus(4)
}
}
setTimeout(() => {
setCurrentVideoUid(uid)
}, 500);
}
// 全员观看
const getShowUser = async (): Promise<void> => {
if (location.href.indexOf('/meeting') !== -1) {
await GetShowUser(state.channelId).then(async (res) => {
if (res.code === 200 && res.data) {
renderVideo(res.data)
}
})
}
}
// 加入房间时间
const changeCurrentSeconds = (): string => {
const duration = dayjs.duration(currentSeconds, 'seconds');
const hours = duration.hours(); // 整数小时
const minutes = duration.minutes(); // 整数分钟
const secondsRemaining = duration.seconds(); // 剩余的秒数
return `${hours > 9 ? hours : '0' + hours}:${minutes > 9 ? minutes : '0' + minutes}:${secondsRemaining > 9 ? secondsRemaining : '0' + secondsRemaining}`
}
// 底部按钮点击效果
const changeFooterListSelect = (row: any, itemIndex: number, rowIndex: number, bool: boolean): void => {
let arr = ['静音', '解除静音', '关闭视频', '开启视频']
if (arr.indexOf(row.title) === -1) {
const footerListTemplate = [...footerList]
footerListTemplate[itemIndex][rowIndex].select = bool;
setFooterList(footerListTemplate)
}
}
// 操作按钮
const changeStatusList = async (row: any, itemIndex: number, rowIndex: number): Promise<void> => {
function requestSpeak() {
confirm({
title: '提示',
icon: <ExclamationCircleFilled />,
content: `该操作需向管理员申请发言权限`,
centered: true,
okText: '申请',
cancelText: '取消',
async onOk() {
GetApplySpeak(state.channelId).then(res => {
if (res.code === 200) {
setIsClicked(true);
message.success('申请发言成功')
}
})
},
onCancel() {
}
})
}
const footerListTemplate = [...footerList]
setFooterListIndex({
itemIndex,
rowIndex,
})
switch (row.title) {
case '成员列表':
setStatusList({
userList: statusList.userList ? false : true,
userChatList: false,
userVideo: false,
})
break;
case '聊天':
setStatusList({
userList: false,
userChatList: statusList.userChatList ? false : true,
userVideo: false,
})
storage.setItem('noViewChatList', 0)
setNoViewChatList(0)
chatScrollBotton()
break;
case '共享屏幕':
await getUserRoomInfo().then(async (res) => {
if (res) {
GetSharedScreen(state.channelId).then(req => {
if (req.code === 200) {
if (res.data) {
setIsShare(res.data)
}
getDesktopCapturerVideo()
setIsSharedScreenModal(true)
}
})
} else {
message.error(msgTips)
}
})
break;
case '停止共享':
case '共享冲突停止共享':
await getUserRoomInfo().then(async (res) => {
if (res) {
await stopScreenCapture()
} else {
message.error(msgTips)
}
})
if (row.title === '停止共享') {
await allUserLook(userInfo.uid, userInfo.userName)
}
break;
case '静音':
await postOpenMicr(false, userInfo.uid)
break;
case '解除静音':
await getUserRoomInfo().then(async (res) => {
if (res) {
await postOpenMicr(true, userInfo.uid)
} else {
if (!isClicked) {
setCurrentRequestSpeakType('audio')
requestSpeak()
} else {
message.error('申请太频繁了,请稍后重试!');
}
}
})
break;
case '关闭视频':
await postOpenCamera(false, userInfo.uid)
break;
case '开启视频':
await getUserRoomInfo().then(async (res) => {
if (res) {
await postOpenCamera(true, userInfo.uid)
} else {
if (!isClicked) {
setCurrentRequestSpeakType('video')
requestSpeak()
} else {
message.error('申请太频繁了,请稍后重试!');
}
}
})
break;
case '设置':
await getUserRoomInfo().then(async (res) => {
stupWizardRef.current.changeModal(0, res)
})
break;
case '邀请人员':
await getUserRoomInfo().then(async (res) => {
if (res) {
invitingPersonnelRef.current.changeInvitingPersonnelModal()
} else {
message.error(msgTips)
}
})
break;
case '录制':
const setting = await JSON.parse(storage.getItem('setting') as string);
try {
if (!isClickedMediaSteam) {
setIsClickedMediaSteam(true)
await fs.access(setting.recordingFilesPath, fs.constants.F_OK);
footerListTemplate[itemIndex][rowIndex].title = '录制中';
footerListTemplate[itemIndex][rowIndex].active = true;
setFooterList(footerListTemplate);
window.electron.getSources().then(async (sources: any) => {
const screenId = sources[0].id;
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: screenId,
}
} as any,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: screenId,
}
} as any
});
// 获取所有音频输入设备
const devices = await navigator.mediaDevices.enumerateDevices();
const audioInputDevices = devices.filter(device => device.kind === 'audioinput' &&
device.deviceId !== 'default' &&
device.deviceId !== 'communications');
// 使用Web Audio API来捕获系统声音和麦克风声音将它们合并到同一个MediaStream中。
const audioCtx = new (window.AudioContext || (window as any).webkitAudioContext)();
const systemSoundSource = audioCtx.createMediaStreamSource(stream);
const systemSoundDestination = audioCtx.createMediaStreamDestination();
systemSoundSource.connect(systemSoundDestination);
// 录制所有音频输入设备
audioInputDevices.forEach(async device => {
const micStream = await navigator.mediaDevices.getUserMedia({ audio: { deviceId: { exact: device.deviceId } } });
setMediaStream((res: any) => [...res, micStream]);
const micSoundSource = audioCtx.createMediaStreamSource(micStream);
micSoundSource.connect(systemSoundDestination);
})
// 合并音频流与视频流
const combinedSource = new MediaStream([...stream.getVideoTracks(), ...systemSoundDestination.stream.getAudioTracks()]);
// 开始录制
const mediaRecorder = new MediaRecorder(combinedSource, {
mimeType: 'video/webm;codecs=vp9,opus',
videoBitsPerSecond: 1500,
});
setRecorder(mediaRecorder);
});
} else {
message.error('录制太频繁了,请稍后重试!');
}
} catch (error: any) {
if (error.code === 'ENOENT') {
message.error({
content: <div> <span style={{ color: '#606fc7', cursor: 'pointer' }} onClick={() => {
stupWizardRef.current.changeModal(3);
}}></span></div>
});
return;
} else {
message.error(error);
}
}
break;
case '录制中':
if (isClickedMediaSteam) {
message.error('录制时长不足3秒请稍后重试');
} else {
footerListTemplate[itemIndex][rowIndex].title = '录制'
footerListTemplate[itemIndex][rowIndex].active = false
setFooterList(footerListTemplate)
stopRecorderMedia()
}
break;
case '共享文件':
sharedFilesModelRef.current.getData()
break;
case '申请发言':
if (!isClicked) {
requestSpeak()
} else {
message.error('申请太频繁了,请稍后重试!');
}
break;
case '会议监控':
if (!statusList.userVideo) {
GetRoomRtcToken(state.channelId + 'a').then(async res => {
if (res.code === 200) {
await agora.allJoinChannelEx(true, res.data)
}
})
} else {
await agora.allLeaveChannelEx()
}
setStatusList({
userVideo: statusList.userVideo ? false : true,
userChatList: false,
userList: false,
})
storage.setItem('noViewChatList', 0)
setNoViewChatList(0)
break;
case '签到':
singInRef.current.changeModal()
break;
}
}
// 停止录制
const stopRecorderMedia = async (): Promise<void> => {
setRecorder((res: any) => {
if (res) {
try {
res.stop()
} catch (error) {
}
}
return res
})
setMediaStream((res: any) => {
if (res.length) {
res.forEach((item: any) => {
item.getTracks().forEach((track: any) => {
try {
track.stop()
} catch (error) {
}
});
});
}
return res
})
}
// 退出房间
const leaveChannel = async (bool: boolean = true): Promise<void> => {
setIsLeave(true)
await stopScreenCapture()
await stopRecorderMedia()
if (bool) {
await getLeave()
}
await agora.leaveChannel()
setRecorder((res: any) => {
if (res) {
} else {
if (userInfo.isAnonymous) {
storage.setItem('userLogin', false)
} else {
navigate(`/home/index`, {
state: {
currentSeconds
}
})
}
}
return res
})
}
// 分享屏幕
const clickSharedScreen = async (): Promise<void> => {
const elements = document.querySelectorAll('.intersectionObserver-view');
if (elements.length) {
elements.forEach(item => {
if (item.id !== userInfo.uid) {
agora.muteRemoteVideoStreamEx(Number(item.id), true)
}
});
agora.setSubscribeVideoBlocklist([Number(user.screenShareId)], 1)
}
GetRoomRtcToken(state.channelId).then(async res => {
if (res.code === 200) {
await agora.destroyRendererByView(`video-source-camera-primary`)
await agora.destroyRendererByView(`video-source-screen`)
await agora.destroyRendererByView(`video-source-remote-screen`)
await agora.destroyRendererByView(`video-source-remote-camera`)
const footerListTemplate = [...footerList]
footerListTemplate[footerListIndex.itemIndex][footerListIndex.rowIndex].title = '停止共享'
setIsSharedScreenModal(false)
await agora.setDesktopCapturerVideo(sharedScreenItem, isComputerAudio, isFluencyPriority, res.data)
await allUserLook(user.screenShareId, user.userName)
const isOpen = await getKeyOpenChildWindow('shareScreenWindow')
setIsScreenCapture(true)
if (!isOpen) {
window.electron.createChildWindow('show')
setKeyOpenChildWindow('shareScreenWindow', true)
window.electron.setMainWindowSize({
width: 250,
height: 160,
})
window.electron.setPosition('right')
}
}
})
}
// 获取桌面可共享屏幕的引用
const getDesktopCapturerVideo = (): void => {
agora.getDesktopCapturerVideo({ width: 50, height: 50 }, { width: 30, height: 30 }, true).then((res: any) => {
res.forEach((item: any, index: number) => {
if (item.type === 1) {
item.sourceTitle = '桌面' + (index + 1)
}
if (item.thumbImage.buffer) {
item.thumbnailUrl = thumbImageBufferToBase64(item.thumbImage)
}
if (item.iconImage.buffer) {
item.iconDataUrl = thumbImageBufferToBase64(item.iconImage)
}
})
setSharedScreenList(res)
})
};
// 设置全员看谁
const allUserLook = async (uid: string, name: string): Promise<void> => {
await PostShowUser(state.channelId, uid, name)
}
// 设置发言人
const postRoomManager = async (data: any): Promise<void> => {
if (isAdmin >= 20) {
message.error('设置失败已达最大发言人20个')
} else {
await PostRoomManager({
roomId: data.roomId,
roomNum: data.roomNum,
userId: data.userId
})
}
}
// 停止共享
const stopScreenCapture = async (): Promise<void> => {
await agora.destroyRendererByView(`meetingAbsoluteVideo`)
const footerListTemplate = [...footerList]
await agora.leaveChannelEx(userInfo.screenShareId)
agora.stopScreenCapture()
footerListTemplate[1][0].title = '共享屏幕'
setFooterList(footerListTemplate)
window.electron.createChildWindow('stop')
setKeyOpenChildWindow('shareScreenWindow', false)
window.electron.setChildWindowShow({
key: 'noticeWindow',
bool: false
})
setIsScreenCapture(bool => {
if (bool) {
window.electron.setViewStatus('show')
window.electron.getWindowSize().then((res: any) => {
window.electron.setMainWindowSize({
width: Math.ceil(res.width / 1.5),
height: Math.ceil(res.height / 1.3),
})
window.electron.getIsMaximized().then((b: boolean) => {
if (!b) {
window.electron.setViewStatus('maximize')
}
})
})
}
return false
})
}
// 获取房间用户
const getRoomUser = async (): Promise<void> => {
GetRoomUser(state.channelId).then(res => {
if (res.code === 200) {
res.data.forEach((item: any) => {
item.isShow = true;
item.isRoom = true;
item.isAdmin = role.ID.includes(item.roleId) || item.isRoomManager
})
setRoomUserList(res.data)
getUserRoomInfo().then(async (res) => {
await agora.updateChannelMediaOptions(res ? true : false)
changeAgoraDevice()
})
}
})
}
// 监听缓存变化
const handleCustomStorageChange = async (e: any): Promise<void> => {
switch (e.key) {
case 'meetingMode':
setMeetingMode(e.value)
break;
case 'quitMeeting':
if (e.value) {
setQuitMeetingModal(true)
}
break;
case 'reconnect':
if (e.value == true) {
message.success('网络已连接。')
meetingDisconnectedRef.current.changeModal(false)
setIsAgoraDisconnected(false)
async function setUserStatus(res: any) {
let userItem = res.find((item: any) => item.uid === userInfo.uid)
await window.electron.onInvoke('joinChannel', {
roomNum: state.channelId,
enableMicr: !storeDevice[0][0].active,
enableCamera: !storeDevice[0][1].active,
isRoomManager: userItem ? userItem.isRoomManager : false,
})
await getShowUser()
if (userItem.isRoomManager) {
await postOpenMicr(!storeDevice[0][0].active, userInfo.uid)
await postOpenCamera(!storeDevice[0][1].active, userInfo.uid)
}
setIsNetworkDisconnected(bool => {
if (bool) {
allUserLook(userItem.uid, userItem.userName)
}
return false
})
setIsScreenCapture(bool => {
if (bool) {
allUserLook(userItem.screenShareId, userItem.userName)
}
return bool
})
}
setRoomUserList((res: any) => {
setUserStatus(res)
return res
})
} else {
storeDevice = JSON.parse(JSON.stringify(footerList))
}
break;
}
};
// 聊天发送
const sendMsg = (text?: string): void => {
let msg = text ? text : textMsg;
if (msg) {
window.electron.onInvoke('sendChannelMsg', {
roomNum: state.channelId,
msg: msg,
})
let item = {
uid: userInfo.uid,
userName: userInfo.userName,
message: msg,
timestamp: +new Date()
}
setChatList((newChatList: any) => [...newChatList, item])
window.electron.windowHandleMessage({
key: 'chatSmallWindow',
parmes: {
chatListIten: item,
}
})
setTextMsg('');
chatScrollBotton()
} else {
message.error('请输入内容!')
}
}
// 聊天框滚动到底部
const chatScrollBotton = (): void => {
setTimeout(() => {
const meetingUserChatContentView = document.getElementById('meetingUserChatContentView') as HTMLElement;
if (meetingUserChatContentView) {
meetingUserChatContentView.scrollTop = meetingUserChatContentView.scrollHeight;
}
}, 0);
}
// 设备管理
const equipmentManagement = async (uid: string, userName: string): Promise<void> => {
equipmentManagementRef.current.changeModal(uid, userName)
}
// 开关麦克风
const postOpenMicr = async (enableMicr: boolean, uid: string, isAll: boolean = false): Promise<void> => {
await getUserRoomInfo().then(async (res) => {
if (res) {
if (!isAll) {
let msg = '';
if (uid === user.uid) {
await agora.getAudioMediaList().then(res => {
if (!res.ecordingList.length) {
msg = '未检测到麦克风!'
}
})
}
if (msg) {
message.error(msg)
return
}
}
if (enableMicr) {
const enableMicrLenght = roomUserList.filter((item: any) => item.enableMicr).length
if (enableMicrLenght >= 20) {
return message.error('房间内最多20个开启麦克风')
}
}
await postOpenMicrApi(enableMicr, uid, isAll, true)
} else {
message.error(msgTips)
}
})
}
// 开关麦克风
const postOpenMicrApi = async (enableMicr: boolean, uid: string, isAll: boolean, isMessage: boolean = false): Promise<void> => {
if (isAll) {
await PostMuteAll({
roomNum: state.channelId,
enableMicr
})
} else {
await PostOpenMicr({
roomNum: state.channelId,
uid,
enableMicr
})
}
if (isMessage) {
// message.success('操作成功')
}
}
// 开关视频
const postOpenCamera = async (enableCamera: boolean, uid: string): Promise<void> => {
await getUserRoomInfo().then(async (res) => {
if (res) {
let msg = '';
if (uid === user.uid) {
await agora.getVideoDeviceManager().then(res => {
if (!res.list.length) {
msg = '未检测到摄像头!'
}
})
}
if (msg) {
message.error(msg)
return
}
if (enableCamera) {
const enableCameraLenght = roomUserList.filter((item: any) => item.enableCamera).length
if (enableCameraLenght >= 20) {
return message.error('房间内最多20个开启摄像头')
}
}
await postOpenCameraApi(enableCamera, uid, true)
} else {
message.error(msgTips)
}
})
}
// 开关视频
const postOpenCameraApi = async (enableCamera: boolean, uid: string, isMessage: boolean = false): Promise<void> => {
if (enableCamera) {
await agora.startCameraCapture()
} else {
await agora.stopCameraCapture();
}
await PostOpenCamera({
roomNum: state.channelId,
uid,
enableCamera
})
if (isMessage) {
// message.success('操作成功')
}
}
// 演讲者模式
// const changeSpeakerMode = (): void => {
// speakerModeModalRef.current.changeSpeakerMode()
// }
// 获取当前用户在房间的角色信息
const getUserRoomInfo = async (): Promise<any> => {
return new Promise((resolve, _reject) => {
setRoomUserList((res: any) => {
let userItem = res.find((item: any) => item.uid === userInfo.uid)
if (userItem && (role.ID.includes(userItem.roleId) || userItem.isRoomManager)) {
resolve(userItem)
} else {
resolve('')
}
return res
})
})
}
// 获取当前模式样式
const getMeetingContentBodyLeftModeClass = (): string => {
switch (meetingMode) {
case 'FreedomMode':
return styles.meetingContentBodyLeftFreedomMode
case 'StandardMode':
return styles.meetingContentBodyLeftStandardMode
case 'SpeakerMode':
return styles.meetingContentBodyLeftSpeakerMode
case 'SingleScreenMode':
return styles.meetingContentBodyLeftSingleScreenMode
case 'DualScreenMode':
return styles.meetingContentBodyLeftDualScreenMode
case 'FourScreenMode':
return styles.meetingContentBodyLeftFourScreenMode
}
return ''
}
// 获取当前模式文字
const getMeetingContentBodyLeftModeText = (): string => {
switch (meetingMode) {
case 'FreedomMode':
return '自由者模式'
case 'StandardMode':
return '标准模式'
case 'SpeakerMode':
return '演讲模式'
case 'SingleScreenMode':
return '单画面模式'
case 'DualScreenMode':
return '二分屏模式'
case 'FourScreenMode':
return '四分屏模式'
}
return ''
}
// 设置单个视频样式
const setMeetingContentSwiperCardClass = (): string => {
switch (meetingMode) {
case 'StandardMode':
return styles.meetingContentSwiperCardStandardMode
case 'SpeakerMode':
return styles.meetingContentSwiperCardSpeakerMode
}
return ''
}
// 视频是否全屏
const setMeetingContentSwiperCardFullScreenClass = (): string => {
switch (meetingMode) {
case 'StandardMode':
return isVideoFullScreen ? styles.meetingContentSwiperCardStandardModeFullScreen : ''
case 'SpeakerMode':
return isVideoFullScreen ? styles.meetingContentSwiperCardSpeakerModeFullScreen : ''
}
return ''
}
// 获取展开折叠按钮
const getSettingIcon = (): any => {
switch (meetingMode) {
case 'StandardMode':
if (isVideoFullScreen) {
return <div className={`${styles.standardModeIcon} drag`} style={{ top: '-8px' }} title='展开' onClick={() => setIsVideoFullScreen(false)}>
<svg width="22" height="13" viewBox="0 0 22 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.44484 0.435547L0.285156 1.59521L10.9996 12.3075L21.7129 1.59521L20.5532 0.435547L10.9989 9.99002L1.44484 0.435547Z" />
</svg>
</div> //下
} else {
return <div className={`${styles.standardModeIcon} drag`} style={{ top: '152px' }} title='收起' onClick={() => setIsVideoFullScreen(true)}>
<svg width="22" height="13" viewBox="0 0 22 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.5532 12.3066L21.7129 11.147L10.9984 0.434698L0.285156 11.147L1.44481 12.3066L10.9991 2.75217L20.5532 12.3066Z" />
</svg>
</div> //上
}
case 'SpeakerMode':
if (isVideoFullScreen) {
return <div className={`${styles.speakerModeIcon} drag`} style={{ left: '-1px' }} title='展开' onClick={() => setIsVideoFullScreen(false)}>
<svg width="12" height="22" viewBox="0 0 12 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.0625 20.5679L1.22216 21.7275L11.9344 11.0131L1.22216 0.299805L0.0625002 1.45946L9.61697 11.0138L0.0625 20.5679Z" />
</svg>
</div> //右
} else {
return <div className={`${styles.speakerModeIcon} drag`} style={{ left: 'calc(18% - 4px)' }} title='收起' onClick={() => setIsVideoFullScreen(true)}>
<svg width="12" height="22" viewBox="0 0 12 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.9355 1.45949L10.7759 0.299805L0.0636029 11.0143L10.7759 21.7275L11.9355 20.5679L2.38107 11.0136L11.9355 1.45949Z" />
</svg>
</div> //左
}
}
}
// 加入房间
const getJoin = async (enableMicr: boolean, enableCamera: boolean): Promise<void> => {
// await GetJoin({
// roomNum: state.channelId,
// enableMicr,
// enableCamera
// })
await window.electron.onInvoke('joinChannel', {
roomNum: state.channelId,
enableMicr,
enableCamera
})
}
// 离开房间
const getLeave = async (): Promise<void> => {
// await GetLeave({
// roomNum: state.channelId,
// })
await window.electron.onInvoke('levelChannel', {
roomNum: state.channelId
})
}
// 设置全员观看
const setAllUserLook = async (item: any): Promise<void> => {
if (isShare) {
await allUserLook(String(isShare) === item.screenShareId ? item.screenShareId : item.uid, item.userName)
} else {
await allUserLook(item.uid, item.userName)
}
message.success('操作成功')
}
// 移出房间
const getRoomKickout = async (channelId: string, uid: string, userName: string): Promise<void> => {
confirm({
title: '移出会议',
icon: <ExclamationCircleFilled />,
content: `确定将用户${userName}移出会议?`,
centered: true,
okText: '确定',
cancelText: '取消',
async onOk() {
await GetRoomKickout(channelId, uid)
message.success('操作成功')
},
onCancel() {
},
});
}
return (
<>
<div className={styles.meeting} onClick={() => {
setContextMenu('')
setIsNetworkQuality(false)
}}>
{isScreenCapture ? <div className={`${styles.meetingAbsolute}`} id='meetingAbsoluteVideo'>
<div style={{ top: '0px' }} className={`${styles.meetingAbsoluteText}`}>{currentSpeakUser.length ? '正在说话:' + currentSpeakUser.join(';') : '正在说话:'}</div>
{footerList[0][1].active ? <div className={styles.meetingAbsoluteLoading}>
<Avatar name={user.userName} />
</div> : null}
<div style={{ bottom: '0px' }} className={`${styles.meetingAbsoluteText} drag`} onClick={() => {
setIsExpand(!isExpand)
window.electron.setChildWindow({
height: isExpand ? 160 : 40,
key: 'main'
})
}}><span>{isExpand ? '展开' : '收起'}</span></div>
</div> : null}
{contextMenu ? <div className={styles.meetingContentSwiperCardPopover} style={
{
backgroundColor: '#07090B',
position: 'absolute',
top: contextMenuStyle.top,
left: contextMenuStyle.left,
zIndex: 4,
padding: 5
}
}>
{contextMenu.isRoomManager || role.ID.includes(contextMenu.roleId) ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={(event) => {
event.stopPropagation();
setContextMenu('')
setAllUserLook(contextMenu)
}}
>Ta</Button> : null}
{contextMenu.uid !== user.uid && !role.ID.includes(contextMenu.roleId) ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={(event) => {
event.stopPropagation();
setContextMenu('')
if (contextMenu.isRoomManager) {
DeleteRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: contextMenu.uid
})
} else {
postRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: contextMenu.uid
})
}
}}
>{contextMenu.isRoomManager ? '取消发言' : '允许发言'}</Button> : null}
{contextMenu.isRoomManager ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={(event) => {
event.stopPropagation();
setContextMenu('')
postOpenMicr(!contextMenu.enableMicr, contextMenu.uid)
}}
>{contextMenu.enableMicr ? '静音' : '解除静音'}</Button> : null}
{contextMenu.isRoomManager ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={(event) => {
event.stopPropagation();
setContextMenu('')
postOpenCamera(!contextMenu.enableCamera, contextMenu.uid)
}}
>{contextMenu.enableCamera ? '关闭视频' : '打开视频'}</Button> : null}
{contextMenu.uid !== user.uid ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={(event) => {
event.stopPropagation();
setContextMenu('')
equipmentManagement(contextMenu.uid, contextMenu.userName)
}}
></Button> : null}
{contextMenu.uid !== user.uid ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={(event) => {
event.stopPropagation();
setContextMenu('')
userNameRef.current.changeModal({
userName: contextMenu.userName,
uid: contextMenu.uid
})
}}
></Button> : null}
{contextMenu.uid !== user.uid ? <Button
type="primary"
style={{ backgroundColor: '#EC3C3C' }}
size={'small'}
onClick={(event) => {
event.stopPropagation();
setContextMenu('')
getRoomKickout(state.channelId, contextMenu.uid, contextMenu.userName)
}}
></Button> : null}
</div> : null}
{contextHolder}
<div className={styles.meetingHeader}>
<div>
{isScreenCapture ? null : <Popover
open={isNetworkQuality}
content={
<div style={{ color: 'white' }} onMouseLeave={() => setIsNetworkQuality(false)}>
<div>
<span></span>
<span>{networkQuality.level}</span>
<span>
<Popover
content={
<span
style={{ color: 'white' }}>
{networkQuality.text}
</span>
}
title=""
>
<QuestionCircleOutlined style={{
cursor: 'pointer',
marginLeft: '10px'
}} />
</Popover>
</span>
</div>
<div>
<span></span>
<span>{networkOther.txKBitRate}kbps {networkOther.rxKBitRate}kbps</span>
</div>
<div>
<span></span>
<span>{networkOther.txPacketLossRate}% {networkOther.rxPacketLossRate}%</span>
</div>
<div>
<span></span>
<span>{networkOther.lastmileDelay}ms</span>
</div>
</div>
}
title=""
trigger="hover"
>
<div className='drag'
onMouseEnter={() => setIsNetworkQuality(true)}
>
{networkIcon(currentEffective)}
<span></span>
</div>
</Popover>}
<div>{changeCurrentSeconds()}</div>
</div>
<div>{state.channelId} {state.roomName}</div>
<div className='drag'>
<Popover
content={
<div className='modePopover'>
<div onClick={() => {
setModeOpen(false)
storage.setItem('meetingMode', 'StandardMode')
}}>
<img src={ImageUrl.icon43} alt="" />
<span></span>
</div>
<div onClick={() => {
setModeOpen(false)
storage.setItem('meetingMode', 'SpeakerMode')
}}>
<img src={ImageUrl.icon44} alt="" />
<span></span>
</div>
<div onClick={() => {
setModeOpen(false)
}}>
<span></span>
</div>
</div>
}
title=""
trigger="click"
open={modeOpen}
onOpenChange={() => setModeOpen(true)}
>
<div className={styles.meetingGrayButton}>
{meetingMode === 'StandardMode' ? <img src={ImageUrl.icon43} alt="" /> : <img src={ImageUrl.icon44} alt="" />}
<span>{getMeetingContentBodyLeftModeText()}</span>
</div>
</Popover>
<Operation></Operation>
</div>
</div>
<div className={styles.meetingContent}>
<div className={styles.meetingContentBody}>
<div className={`${styles.meetingContentBodyLeft} drag`}>
{isAdmin && currentLookUserAccount ? getSettingIcon() : null}
<div className={getMeetingContentBodyLeftModeClass()} id='videoView' style={meetingMode === 'SpeakerMode' && isVideoFullScreen ? { width: '0' } : {}}>
{roomUserList.map((item: any, index: number) => {
return (item.isRoom && item.isAdmin ? <div
id={item.uid}
className={`${styles.meetingContentSwiperCard} intersectionObserver-view`}
key={index}
onClick={() => {
if (String(isShare) === item.screenShareId) {
renderVideo(item.screenShareId)
} else {
renderVideo(item.uid)
}
}}
onContextMenu={(e: any) => {
if (role.ID.includes(userInfo.roleId)) {
setContextMenuStyle({
top: e.clientY,
left: e.clientX,
})
setContextMenu(item)
}
}}
>
<div className={`${styles.meetingContentSwiperCardVdeio} ${(item.uid === currentVideoId) && !isVideoFullScreen ? styles.active : ''} ${(role.ID.includes(item.roleId) || item.isRoomManager) && !isVideoFullScreen ? styles.boxShadow : ''}`} id={`video-${item.uid}`}>
<div className={styles.meetingContentSwiperCardVdeioLoading}>
<Avatar name={item.userName} />
</div>
</div>
{meetingContentUser(item)}
{item.enableCamera ? null : meetingContentError(item)}
{String(isShare) === item.screenShareId ? <div className={styles.meetingContentSwiperCardShare}>
</div> : null}
{role.ID.includes(user.roleId) ? <Popover placement="bottom" title={''} content={
<div className={styles.meetingContentSwiperCardPopover}>
{item.isRoomManager || role.ID.includes(item.roleId) ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={(event) => {
event.stopPropagation();
setAllUserLook(item)
}}
>Ta</Button> : null}
{item.uid !== user.uid && !role.ID.includes(item.roleId) ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={(event) => {
event.stopPropagation();
if (item.isRoomManager) {
DeleteRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: item.uid
})
} else {
postRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: item.uid
})
}
}}
>{item.isRoomManager ? '取消发言' : '允许发言'}</Button> : null}
{item.isRoomManager ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={(event) => {
event.stopPropagation();
postOpenMicr(!item.enableMicr, item.uid)
}}
>{item.enableMicr ? '静音' : '解除静音'}</Button> : null}
{item.isRoomManager ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={(event) => {
event.stopPropagation();
postOpenCamera(!item.enableCamera, item.uid)
}}
>{item.enableCamera ? '关闭视频' : '打开视频'}</Button> : null}
{item.uid !== user.uid ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={(event) => {
event.stopPropagation();
equipmentManagement(item.uid, item.userName)
}}
></Button> : null}
{item.uid !== user.uid ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={(event) => {
event.stopPropagation();
userNameRef.current.changeModal({
userName: item.userName,
uid: item.uid
})
}}
></Button> : null}
{item.uid !== user.uid ? <Button
type="primary"
style={{ backgroundColor: '#EC3C3C' }}
size={'small'}
onClick={(event) => {
event.stopPropagation();
getRoomKickout(state.channelId, item.uid, item.userName)
}}
></Button> : null}
</div>
}>
<div className={styles.meetingContentOperation}>
<EllipsisOutlined style={{
color: '#fff',
fontSize: '20px'
}} />
</div>
</Popover> : null}
</div> : null)
}
)}
{isAdmin > 6 ? <div>
{meetingMode === "StandardMode" ? <div className={`${styles.meetingContentSwiperCaret}`} style={{ left: '20px', top: '66px' }} onClick={() => {
const container = document.getElementById('videoView') as HTMLElement;
container.scrollLeft -= 100
}}>
<CaretLeftOutlined />
</div> : <div className={`${styles.meetingContentSwiperCaret}`} style={{ left: '8.2%', top: '20px' }} onClick={() => {
const container = document.getElementById('videoView') as HTMLElement;
container.scrollTop -= 100
}}>
<CaretUpOutlined />
</div>}
{meetingMode === "StandardMode" ? <div className={`${styles.meetingContentSwiperCaret}`} style={{ right: '20px', top: '66px' }} onClick={() => {
const container = document.getElementById('videoView') as HTMLElement;
container.scrollLeft += 100
}}>
<CaretRightOutlined />
</div> : <div className={`${styles.meetingContentSwiperCaret}`} style={{ left: '8.2%', bottom: '20px' }} onClick={() => {
const container = document.getElementById('videoView') as HTMLElement;
container.scrollTop += 100
}}>
<CaretDownOutlined />
</div>}
</div> : null}
{currentLookUserStatus === 0 ?
<div className={`${styles.meetingContentSwiperCard} ${setMeetingContentSwiperCardClass()} ${setMeetingContentSwiperCardFullScreenClass()}`}>
<div className={`${styles.meetingContentSwiperCardVdeio}`} id='video-source-camera-primary'>
{<div className={styles.meetingContentSwiperCardVdeioLoading}>
<img src={ImageUrl.loading} alt="" style={{ width: '50px' }} />
</div>}
</div>
{isVideoFullScreen ? <FullscreenExitOutlined className={styles.meetingContentSwiperCardFullScreenIcon} title='还原大小' onClick={() => setIsVideoFullScreen(false)} /> :
<FullscreenOutlined className={styles.meetingContentSwiperCardFullScreenIcon} title='全屏' onClick={() => setIsVideoFullScreen(true)} />}
{meetingContentUser(currentLookUserAccount, true)}
</div> : null}
{currentLookUserStatus === 1 ?
<div className={`${styles.meetingContentSwiperCard} ${setMeetingContentSwiperCardClass()} ${setMeetingContentSwiperCardFullScreenClass()}`}>
<div className={`${styles.meetingContentSwiperCardVdeio}`} id='video-source-camera-primary'>
{<div className={styles.meetingContentSwiperCardVdeioLoading}>
<Avatar name={currentLookUserAccount.userName} />
</div>}
</div>
{isVideoFullScreen ? <FullscreenExitOutlined className={styles.meetingContentSwiperCardFullScreenIcon} title='还原大小' onClick={() => setIsVideoFullScreen(false)} /> :
<FullscreenOutlined className={styles.meetingContentSwiperCardFullScreenIcon} title='全屏' onClick={() => setIsVideoFullScreen(true)} />}
{meetingContentUser(currentLookUserAccount, true)}
{currentLookUserAccount.enableCamera ? null : meetingContentError(currentLookUserAccount)}
</div> : null}
{currentLookUserStatus === 2 ?
<div className={`${styles.meetingContentSwiperCard} ${setMeetingContentSwiperCardClass()} ${setMeetingContentSwiperCardFullScreenClass()}`}>
<div className={`${styles.meetingContentSwiperCardVdeio}`} id='video-source-screen'>
<div className={styles.meetingContentSwiperCardVdeioLoading}>
<Avatar name={currentLookUserAccount.userName} />
</div>
</div>
{isVideoFullScreen ? <FullscreenExitOutlined className={styles.meetingContentSwiperCardFullScreenIcon} title='还原大小' onClick={() => setIsVideoFullScreen(false)} /> :
<FullscreenOutlined className={styles.meetingContentSwiperCardFullScreenIcon} title='全屏' onClick={() => setIsVideoFullScreen(true)} />}
{meetingContentUser(currentLookUserAccount, true)}
</div> : null}
{currentLookUserStatus === 3 ?
<div className={`${styles.meetingContentSwiperCard} ${setMeetingContentSwiperCardClass()} ${setMeetingContentSwiperCardFullScreenClass()}`}>
<div className={`${styles.meetingContentSwiperCardVdeio}`} id='video-source-remote-screen'>
<div className={styles.meetingContentSwiperCardVdeioLoading}>
<Avatar name={currentLookUserAccount.userName} />
</div>
</div>
{isVideoFullScreen ? <FullscreenExitOutlined className={styles.meetingContentSwiperCardFullScreenIcon} title='还原大小' onClick={() => setIsVideoFullScreen(false)} /> :
<FullscreenOutlined className={styles.meetingContentSwiperCardFullScreenIcon} title='全屏' onClick={() => setIsVideoFullScreen(true)} />}
{meetingContentUser(currentLookUserAccount, true)}
</div> : null}
{currentLookUserStatus === 4 ?
<div className={`${styles.meetingContentSwiperCard} ${setMeetingContentSwiperCardClass()} ${setMeetingContentSwiperCardFullScreenClass()}`}>
<div className={`${styles.meetingContentSwiperCardVdeio}`} id='video-source-remote-camera'>
<div className={styles.meetingContentSwiperCardVdeioLoading}>
<Avatar name={currentLookUserAccount.userName} />
</div>
</div>
{isVideoFullScreen ? <FullscreenExitOutlined className={styles.meetingContentSwiperCardFullScreenIcon} title='还原大小' onClick={() => setIsVideoFullScreen(false)} /> :
<FullscreenOutlined className={styles.meetingContentSwiperCardFullScreenIcon} title='全屏' onClick={() => setIsVideoFullScreen(true)} />}
{meetingContentUser(currentLookUserAccount, true)}
{currentLookUserAccount.enableCamera ? null : meetingContentError(currentLookUserAccount)}
</div> : null}
{isAdmin ? null : <div className={styles.meetingContentBodyLeftBlock}>
<img src={ImageUrl.icon46} alt="" />
</div>}
</div>
</div>
{
(statusList.userList || statusList.userChatList || statusList.userVideo) ? (
<div className={`${styles.meetingContentBodyRight} drag`}>
{statusList.userList ?
<div className={styles.meetingUserList}>
<div className={styles.meetingUserListTitle}>
<span></span>
<img src={ImageUrl.icon18} alt="" onClick={() => {
setStatusList({
userList: false,
userChatList: false,
userVideo: false,
})
}} />
</div>
<div style={{ padding: '0 14px', boxSizing: 'border-box' }}>
<Input
placeholder="请输入用户名"
prefix={<SearchOutlined style={{ color: 'white' }} />}
value={userSearchValue}
onChange={(e) => {
setUserSearchValue(e.target.value)
const newRoomUserList = [...roomUserList]
newRoomUserList.forEach(row => {
if (e.target.value) {
if (row.userName.indexOf(e.target.value) !== -1) {
row.isShow = true;
} else {
row.isShow = false;
}
} else {
row.isShow = true;
}
});
setRoomUserList(newRoomUserList)
}}
/>
</div>
<div className={styles.meetingUserListContent}>
{roomUserList.map((item: any, index: number) => {
return (
item.isShow && item.isRoom ? <div key={index + item.uid}>
<div>
<div><Avatar name={item.userName} /></div>
<span>
{item.userName}{item.uid === user.uid ? '(我)' : ''}
{role.ID.includes(item.roleId) || item.isRoomManager ?
<span style={{ color: '#02B188', marginLeft: '4px' }}>
{role.ID.includes(item.roleId) ? '管理员' : '发言人'}
</span>
: null}
</span>
</div>
<div>
{role.ID.includes(item.roleId) || item.isRoomManager ? <div>
<img src={item.enableMicr ? ImageUrl.icon22 : ImageUrl.icon22Active} alt="" onClick={() => {
postOpenMicr(!item.enableMicr, item.uid)
}} title={item.enableMicr ? '静音' : '解除静音'} />
</div> : null}
{role.ID.includes(item.roleId) || item.isRoomManager ? <div>
<img src={item.enableCamera ? ImageUrl.icon23 : ImageUrl.icon23Active} alt="" onClick={() => {
postOpenCamera(!item.enableCamera, item.uid)
}} title={item.enableCamera ? '关闭视频' : '开启视频'} />
</div> : null}
{item.uid !== user.uid && role.ID.includes(user.roleId) ? <div>
<Popover placement="left" title={''} content={
<div style={{ width: '100px' }}>
<Button
style={{ width: '100%' }}
type="primary"
className='m-ant-btn'
size={'small'}
onClick={() => {
setAllUserLook(item)
}}
>Ta</Button>
<Button
type="primary"
className='m-ant-btn'
style={{ width: '100%', marginTop: '10px' }}
size={'small'}
onClick={() => {
equipmentManagement(item.uid, item.userName)
}}
></Button>
<Button
type="primary"
className='m-ant-btn'
style={{ width: '100%', marginTop: '10px' }}
size={'small'}
onClick={() => {
userNameRef.current.changeModal({
userName: item.userName,
uid: item.uid
})
}}
></Button>
<Button
type="primary"
style={{ backgroundColor: '#EC3C3C', width: '100%', marginTop: '10px' }}
size={'small'}
onClick={() => {
getRoomKickout(state.channelId, item.uid, item.userName)
}}
></Button>
</div>
}>
<EllipsisOutlined style={{ color: '#fff', fontSize: '20px' }} />
</Popover>
</div> : null}
{item.uid !== user.uid && !role.ID.includes(item.roleId) && role.ID.includes(user.roleId) ? <div onClick={() => {
if (item.isRoomManager) {
DeleteRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: item.uid
})
} else {
postRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: item.uid
})
}
}}>
{!item.isRoomManager ?
<img src={ImageUrl.icon50} alt="" title='允许发言' /> :
<img src={ImageUrl.icon51} alt="" title='取消发言' />
}
</div> : null}
</div>
</div> : null
)
}
)}
</div>
<div className={`${styles.meetingUserListFooter}`}>
<div onClick={async () => {
await getUserRoomInfo().then(async (res) => {
if (res) {
invitingPersonnelRef.current.changeInvitingPersonnelModal()
} else {
message.error(msgTips)
}
})
}}></div>
<div onClick={() => postOpenMicr(false, user.id, true)}></div>
</div>
</div>
: statusList.userChatList ?
<div className={styles.meetingUserChat} id='meetingUserChat'>
<div className={styles.meetingUserChatTitle}>
<span></span>
<img src={ImageUrl.icon18} alt="" onClick={() => {
setStatusList({
userList: false,
userChatList: false,
userVideo: false,
})
}} />
</div>
<div className={styles.meetingUserChatContent} id='meetingUserChatContentView'>
{chatList.map((item: any, index: number) =>
<div
key={index}
className={`${item.uid !== user.uid ? styles.meetingUserChatContentLeft : styles.meetingUserChatContentRight}`}>
{role.ID.includes(user.roleId) ? <Popover
placement="bottom"
title={''}
onOpenChange={(e: boolean) => {
if (e) {
GetRoomUserItem(state.channelId, item.uid).then((res: any) => {
if (res.code === 200) {
setRoomUserItem(res.data)
}
})
} else {
setRoomUserItem(null)
}
}}
content={
roomUserItem ? <div className={styles.meetingContentSwiperCardPopover}>
{roomUserItem.isRoomManager || role.ID.includes(roomUserItem.roleId) ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={(event) => {
event.stopPropagation();
setAllUserLook(roomUserItem)
}}
>Ta</Button> : null}
{roomUserItem.uid !== user.uid && !role.ID.includes(roomUserItem.roleId) ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={async (event) => {
event.stopPropagation();
if (roomUserItem.isRoomManager) {
await DeleteRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: roomUserItem.uid
})
} else {
await postRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: roomUserItem.uid
})
}
await GetRoomUserItem(state.channelId, item.uid).then((res: any) => {
if (res.code === 200) {
setRoomUserItem(res.data)
}
})
}}
>{roomUserItem.isRoomManager ? '取消发言' : '允许发言'}</Button> : null}
{roomUserItem.isRoomManager ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={async (event) => {
event.stopPropagation();
await postOpenMicr(!roomUserItem.enableMicr, roomUserItem.uid)
await GetRoomUserItem(state.channelId, item.uid).then((res: any) => {
if (res.code === 200) {
setRoomUserItem(res.data)
}
})
}}
>{roomUserItem.enableMicr ? '静音' : '解除静音'}</Button> : null}
{roomUserItem.isRoomManager ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={async (event) => {
event.stopPropagation();
await postOpenCamera(!roomUserItem.enableCamera, roomUserItem.uid)
await GetRoomUserItem(state.channelId, item.uid).then((res: any) => {
if (res.code === 200) {
setRoomUserItem(res.data)
}
})
}}
>{roomUserItem.enableCamera ? '关闭视频' : '打开视频'}</Button> : null}
{roomUserItem.uid !== user.uid ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={(event) => {
event.stopPropagation();
equipmentManagement(roomUserItem.uid, roomUserItem.userName)
}}
></Button> : null}
{roomUserItem.uid !== user.uid ? <Button
type="primary"
style={{ backgroundColor: '#EC3C3C' }}
size={'small'}
onClick={(event) => {
event.stopPropagation();
getRoomKickout(state.channelId, roomUserItem.uid, roomUserItem.userName)
}}
></Button> : null}
</div> : <div style={{ color: 'white' }}></div>
}>
<div>
<div><Avatar name={item.userName} /></div>
{item.uid !== user.uid ?
<span>{item.userName} <span style={{ fontSize: '12px', color: '#ccc', marginLeft: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')}</span></span> :
<span> <span style={{ fontSize: '12px', color: '#ccc', marginRight: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')} </span>{item.userName}</span>
}
</div>
</Popover> : <div>
<div><Avatar name={item.userName} /></div>
{item.uid !== user.uid ?
<span>{item.userName}<span style={{ fontSize: '12px', color: '#ccc', marginLeft: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')}</span></span> :
<span><span style={{ fontSize: '12px', color: '#ccc', marginRight: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')} </span>{item.userName}</span>
}
</div>}
<div>{item.message}</div>
</div>
)}
</div>
<div className={`${styles.meetingUserChatButton}`}>
{
commonlyChatList.map((item: string, index: number) => {
return <Button
key={index}
type="primary"
className='m-ant-btn'
onClick={() => {
sendMsg(item)
}}
>{item}</Button>
})
}
</div>
<div className={`${styles.meetingUserChatInput}`}>
<Input.TextArea placeholder="请输入消息" value={textMsg} style={{ flexGrow: 1 }} onChange={(e) => {
setTextMsg(e.target.value)
}}></Input.TextArea>
<Button type="primary" className='m-ant-btn' style={{ flexShrink: 0, marginTop: '4px' }} onClick={() => sendMsg()}></Button>
</div>
</div>
:
<div className={styles.meetingUserVideoList} style={{ width: statusList.userVideo ? '500px' : '500px' }}>
<div className={styles.meetingUserVideoListTitle}>
<span></span>
<img src={ImageUrl.icon18} alt="" onClick={async () => {
await agora.allLeaveChannelEx()
setStatusList({
userList: false,
userChatList: false,
userVideo: false,
})
}} />
</div>
<div className={styles.meetingUserVideoListContent}>
<UserVideo />
</div>
</div>
}
</div>
) : null
}
</div>
{isScreenCapture ? null : <div className={styles.meetingContentFooter}>
{footerList.map((item: any, itemIndex: number) => {
return (
<div key={itemIndex}>
{item.map((row: any, rowIndex: number) => {
switch (row.title) {
case '结束':
return <Popover key={rowIndex}
content={
<div className='meetingContentFooterPopover'>
{role.ID.includes(user.roleId) ?
<Popconfirm
title="提示"
description={`结束会议后,所有人将退出,是否结束?`}
onConfirm={async () => {
GetLeaveAll({
roomNum: state.channelId,
})
}}
onCancel={() => {
}}
okText="结束"
cancelText="取消"
>
<div className='meetingContentFooterPopoverDel'></div>
</Popconfirm>
: null}
<div className='meetingContentFooterPopoverDefault' onClick={() => leaveChannel()}></div>
<div className='meetingContentFooterPopoverCancel' onClick={() => { setOpen(false) }}></div>
</div>
}
title=""
trigger="click"
open={open}
onOpenChange={() => setOpen(true)}
>
<div
className='drag'
onMouseDown={() => changeFooterListSelect(row, itemIndex, rowIndex, true)}
onMouseUp={() => changeFooterListSelect(row, itemIndex, rowIndex, false)}
onMouseLeave={() => changeFooterListSelect(row, itemIndex, rowIndex, false)}
>
{row.select ? <img src={row.iconSelect} alt="" /> : <img src={row.active ? row.iconActive : row.icon} alt="" />}
<span>{row.title}</span>
</div>
</Popover>
case '签到':
if (!role.ID.includes(user.roleId)) {
return <div className='drag'
onClick={() => changeStatusList(row, itemIndex, rowIndex)}
onMouseDown={() => changeFooterListSelect(row, itemIndex, rowIndex, true)}
onMouseUp={() => changeFooterListSelect(row, itemIndex, rowIndex, false)}
onMouseLeave={() => changeFooterListSelect(row, itemIndex, rowIndex, false)}
key={rowIndex}>
{row.select ? <img src={row.iconSelect} alt="" /> : <img src={row.icon} alt="" />}
<span>{row.title}</span>
</div>
}
return null
case '申请发言':
// if (!role.ID.includes(user.roleId)) {
// return <div className='drag' onClick={() => changeStatusList(row, itemIndex, rowIndex)} key={rowIndex}>
// {row.select ? <img src={row.iconSelect} alt="" /> : <img src={row.active ? row.iconActive : row.icon} alt="" />}
// <span>{row.title}</span>
// </div>
// }
return null
case '结束发言':
if (!role.ID.includes(user.roleId)) {
return <Popconfirm
key={rowIndex}
title="提示"
description="确定要结束发言吗?"
onConfirm={() => {
DeleteRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: userInfo.uid
})
}}
okText="确定"
cancelText="取消"
>
<div className='drag' key={rowIndex}>
<img src={row.active ? row.iconActive : row.icon} alt="" />
<span>{row.title}</span>
</div>
</Popconfirm>
}
return null
case '会议监控':
if (role.ID.includes(user.roleId)) {
return <div
className='drag'
onClick={() => changeStatusList(row, itemIndex, rowIndex)}
onMouseDown={() => changeFooterListSelect(row, itemIndex, rowIndex, true)}
onMouseUp={() => changeFooterListSelect(row, itemIndex, rowIndex, false)}
onMouseLeave={() => changeFooterListSelect(row, itemIndex, rowIndex, false)}
key={rowIndex}>
{statusList.userVideo ? <img src={row.iconSelect} alt="" /> : row.select ? <img src={row.iconSelect} alt="" /> : <img src={row.active ? row.iconActive : row.icon} alt="" />}
<span>{row.title}</span>
</div>
}
return null
case '成员列表':
return <div
className='drag'
onClick={() => changeStatusList(row, itemIndex, rowIndex)}
onMouseDown={() => changeFooterListSelect(row, itemIndex, rowIndex, true)}
onMouseUp={() => changeFooterListSelect(row, itemIndex, rowIndex, false)}
onMouseLeave={() => changeFooterListSelect(row, itemIndex, rowIndex, false)}
key={rowIndex}>
{statusList.userList ? <img src={row.iconSelect} alt="" /> : row.select ? <img src={row.iconSelect} alt="" /> : <img src={row.active ? row.iconActive : row.icon} alt="" />}
<span>{row.title}</span>
{row.title === '成员列表' ? <div>{roomUserList.filter((item: any) => item.isRoom).length}</div> : null}
</div>
case '聊天':
return <div
className='drag'
onClick={() => changeStatusList(row, itemIndex, rowIndex)}
onMouseDown={() => changeFooterListSelect(row, itemIndex, rowIndex, true)}
onMouseUp={() => changeFooterListSelect(row, itemIndex, rowIndex, false)}
onMouseLeave={() => changeFooterListSelect(row, itemIndex, rowIndex, false)}
key={rowIndex}>
{statusList.userChatList ? <img src={row.iconSelect} alt="" /> : row.select ? <img src={row.iconSelect} alt="" /> : <img src={row.active ? row.iconActive : row.icon} alt="" />}
<span>{row.title}</span>
{row.title === '聊天' && noViewChatList > 0 ? <div
style={{
backgroundColor: 'red',
borderRadius: '50%',
}}
>{noViewChatList}</div> : null}
</div>
case '静音':
case '解除静音':
return <div
className='drag'
onClick={() => changeStatusList(row, itemIndex, rowIndex)}
onMouseDown={() => changeFooterListSelect(row, itemIndex, rowIndex, true)}
onMouseUp={() => changeFooterListSelect(row, itemIndex, rowIndex, false)}
onMouseLeave={() => changeFooterListSelect(row, itemIndex, rowIndex, false)}
key={rowIndex}>
<label>
<img src={row.active ? row.iconActive : row.icon} alt="" />
{!row.active ? <div style={{ backgroundImage: `url(${ImageUrl.icon49})` }} id={`micr-item-${userInfo.uid}`}>
</div> : ''}
</label>
<span>{row.title}</span>
</div>
case '录制':
case '录制中':
if (role.ID.includes(user.roleId)) {
return <div
className='drag'
onClick={() => changeStatusList(row, itemIndex, rowIndex)}
onMouseDown={() => changeFooterListSelect(row, itemIndex, rowIndex, true)}
onMouseUp={() => changeFooterListSelect(row, itemIndex, rowIndex, false)}
onMouseLeave={() => changeFooterListSelect(row, itemIndex, rowIndex, false)}
key={rowIndex}>
{row.select ? <img src={row.iconSelect} alt="" /> : <img src={row.active ? row.iconActive : row.icon} alt="" />}
<span>{row.title}</span>
</div>
}
return null
default:
return <div
className='drag'
onClick={() => changeStatusList(row, itemIndex, rowIndex)}
onMouseDown={() => changeFooterListSelect(row, itemIndex, rowIndex, true)}
onMouseUp={() => changeFooterListSelect(row, itemIndex, rowIndex, false)}
onMouseLeave={() => changeFooterListSelect(row, itemIndex, rowIndex, false)}
key={rowIndex}>
{row.select ? <img src={row.iconSelect} alt="" /> : <img src={row.active ? row.iconActive : row.icon} alt="" />}
<span>{row.title}</span>
</div>
}
})}
</div>
)
})}
</div>}
</div >
</div >
<Modal title="共享屏幕" open={isSharedScreenModal} footer={null} closable={false} centered width={'800px'}>
<div className={styles.sharedScreenModal}>
<div>
{sharedScreenList.map((item: any, index: number) => {
return (
<div
className={sharedScreenItem.sourceId === item.sourceId ? styles.active : ''}
key={index}
onClick={() => {
setSharedScreenItem(item)
}}>
{item.iconDataUrl ? <img src={item.iconDataUrl} alt="" /> : ''}
<div><img src={item.thumbnailUrl} alt="" /></div>
<span>{item.sourceTitle}</span>
</div>
)
})}
</div>
<div>
<div>
<Checkbox onChange={(e) => {
setIsComputerAudio(e.target.checked)
}} checked={isComputerAudio}></Checkbox>
<Checkbox onChange={(e) => {
setIsFluencyPriority(e.target.checked)
}} checked={isFluencyPriority}></Checkbox>
</div>
<div>
<Button type="primary" onClick={() => {
setIsSharePopConfirm(false)
setIsSharedScreenModal(false)
}} style={{ backgroundColor: '#31353A', marginRight: '14px' }}></Button>
{isShare && sharedScreenItem ? <Popconfirm
title="提示"
description={`这将停止[${isShareUser?.userName}]的共享,是否继续?`}
open={isSharePopConfirm}
onConfirm={async () => {
let data = sharedScreenList.find((item: any) => item.sourceId === sharedScreenItem.sourceId)
if (data) {
PostSharedScreen(state.channelId).then(res => {
if (res.code === 200) {
setIsSharePopConfirm(false)
clickSharedScreen()
}
})
} else {
message.error('请选择应用!')
}
}}
onCancel={() => {
setIsSharePopConfirm(false)
}}
okText="是"
cancelText="否"
>
<Button type="primary" className='m-ant-btn' onClick={() => {
setIsSharePopConfirm(true)
}}></Button>
</Popconfirm> :
<Button type="primary" className='m-ant-btn' onClick={() => {
let data = sharedScreenList.find((item: any) => item.sourceId === sharedScreenItem.sourceId)
if (data) {
PostSharedScreen(state.channelId).then(res => {
if (res.code === 200) {
clickSharedScreen()
}
})
} else {
message.error('请选择应用!')
}
}}></Button>
}
</div>
</div>
</div>
</Modal>
<Modal
title=""
open={quitMeetingModal}
footer={null}
closable={false}
centered
width={'190px'}
>
<div>
<div className='meetingContentFooterPopover'>
{role.ID.includes(user.roleId) ?
<Popconfirm
title="提示"
description={`结束会议后,所有人将退出,是否结束?`}
onConfirm={async () => {
await GetLeaveAll({
roomNum: state.channelId,
})
}}
onCancel={() => {
}}
okText="结束"
cancelText="取消"
>
<div className='meetingContentFooterPopoverDel'></div>
</Popconfirm>
: null}
<div className='meetingContentFooterPopoverDefault' onClick={async () => {
await leaveChannel()
}}></div>
<div className='meetingContentFooterPopoverCancel' onClick={() => { setQuitMeetingModal(false) }}></div>
</div>
</div>
</Modal>
<SharedFilesModel ref={sharedFilesModelRef} />
<SpeakerModeModal ref={speakerModeModalRef} />
<InvitingPersonnelModal ref={invitingPersonnelRef} />
<StupWizard ref={stupWizardRef} />
<EquipmentManagement ref={equipmentManagementRef} />
<MeetingDisconnected ref={meetingDisconnectedRef} />
<SingIn ref={singInRef} />
<UserName ref={userNameRef} />
</>
)
}
const meetingContentUser = (item: any, bool?: boolean) => {
return (
<>
<div className={styles.meetingContentUser}>
<div className={styles.meetingContentUserName}>
{role.ID.includes(item.roleId) || item.isRoomManager ?
<div style={{ background: role.ID.includes(item.roleId) ? '#FDC229' : '#3F51B5' }}>
<img src={ImageUrl.icon32} alt="" />
</div> : null}
{
bool ? !item.enableMicr ? <img src={item.enableMicr ? ImageUrl.icon22 : ImageUrl.icon22Active} alt="" /> : '' :
<label>
<img src={item.enableMicr ? ImageUrl.icon22 : ImageUrl.icon22Active} alt="" />
{item.enableMicr ? <div style={{ backgroundImage: `url(${ImageUrl.icon49})` }} id={`micr-${item.uid}`}>
</div> : ''}
</label>
}
<span style={{ maxWidth: bool ? '' : '8vw' }} title={`${item.userName}${role.ID.includes(item.roleId) || item.isRoomManager ? role.ID.includes(item.roleId) ? '(管理员)' : '(发言人)' : ''}`}>
{item.userName}
{role.ID.includes(item.roleId) || item.isRoomManager ? role.ID.includes(item.roleId) ? '(管理员)' : '(发言人)' : ''}
</span>
</div>
</div>
</>
)
}
const meetingContentError = (item: any) => {
return (
<>
<div
className={`${styles.meetingContentError}`}
>
<Avatar name={item.userName} />
</div>
</>
)
}
const networkIcon = (network: number) => {
switch (network) {
case 0:
return <svg width="33" height="32" viewBox="0 0 33 32" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ transform: 'scale(0.7)' }}>
<g clip-path="url(#clip0_21262_3609)">
<path d="M4.44094 32C3.03695 32 1.90039 31.15 1.90039 30.1V15.3C1.90039 14.25 3.03695 13.4 4.44094 13.4C5.84492 13.4 6.98149 14.25 6.98149 15.3V30.1C6.98149 31.15 5.84492 32 4.44094 32ZM17.01 32C15.606 32 14.4694 31.15 14.4694 30.1V8.9C14.4694 7.85 15.606 7 17.01 7C18.4139 7 19.5505 7.85 19.5505 8.9V30.1C19.5505 31.15 18.4139 32 17.01 32ZM29.579 32C28.175 32 27.0384 31.15 27.0384 30.1V1.9C27.0384 0.85 28.175 0 29.579 0C30.983 0 32.1195 0.85 32.1195 1.9V30.1C32.1195 31.15 30.983 32 29.579 32Z" fill="#7C8280" />
<path d="M7.00124 2.11509L5.01758 4.09875L3.03391 2.11509C2.77629 1.85747 2.35122 1.85747 2.0936 2.11509C1.83599 2.37271 1.83599 2.79778 2.0936 3.0554L4.07727 5.03906L2.0936 7.02273C1.83599 7.28035 1.83599 7.70542 2.0936 7.96304C2.35122 8.22065 2.77629 8.22065 3.03391 7.96304L5.01758 5.97937L7.00124 7.96304C7.25886 8.22065 7.68393 8.22065 7.94155 7.96304C8.19917 7.70542 8.19917 7.28035 7.94155 7.02273L5.95789 5.03906L7.94155 3.0554C8.19917 2.79778 8.19917 2.37271 7.94155 2.11509C7.68393 1.85747 7.25886 1.85747 7.00124 2.11509Z" fill="#F90000" />
</g>
<defs>
<clipPath id="clip0_21262_3609">
<rect width="32" height="32" fill="white" transform="translate(0.119141)" />
</clipPath>
</defs>
</svg>
case 1:
return <svg width="33" height="32" viewBox="0 0 33 32" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ transform: 'scale(0.7)' }}>
<g clip-path="url(#clip0_21262_3615)">
<path d="M26.9316 30.1C26.9316 31.15 28.0918 32 29.5249 32C30.9581 32 32.1182 31.15 32.1182 30.1V1.9C32.1182 0.85 30.9581 0 29.5249 0C28.0918 0 26.9316 0.85 26.9316 1.9V30.1Z" fill="#7C8280" />
<path d="M14.1035 30.1C14.1035 31.15 15.2637 32 16.6968 32C18.1299 32 19.2901 31.15 19.2901 30.1V8.9C19.2901 7.85 18.1299 7 16.6968 7C15.2637 7 14.1035 7.85 14.1035 8.9V30.1Z" fill="#7C8280" />
<path d="M1.27344 30.1004C1.27344 31.1504 2.4336 32.0004 3.86673 32.0004C5.29986 32.0004 6.46002 31.1504 6.46002 30.1004V15.3004C6.46002 14.2504 5.29986 13.4004 3.86673 13.4004C2.4336 13.4004 1.27344 14.2504 1.27344 15.3004V30.1004Z" fill="#FF800B" />
</g>
<defs>
<clipPath id="clip0_21262_3615">
<rect width="32" height="32" fill="white" transform="translate(0.119141)" />
</clipPath>
</defs>
</svg>
case 2:
return <svg width="33" height="32" viewBox="0 0 33 32" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ transform: 'scale(0.7)' }}>
<g clip-path="url(#clip0_21262_3621)">
<path d="M26.9316 30.1C26.9316 31.15 28.0918 32 29.5249 32C30.9581 32 32.1182 31.15 32.1182 30.1V1.9C32.1182 0.85 30.9581 0 29.5249 0C28.0918 0 26.9316 0.85 26.9316 1.9V30.1Z" fill="#7C8280" />
<path d="M14.1035 30.1C14.1035 31.15 15.2637 32 16.6968 32C18.1299 32 19.2901 31.15 19.2901 30.1V8.9C19.2901 7.85 18.1299 7 16.6968 7C15.2637 7 14.1035 7.85 14.1035 8.9V30.1Z" fill="#FF800B" />
<path d="M1.27344 30.1004C1.27344 31.1504 2.4336 32.0004 3.86673 32.0004C5.29986 32.0004 6.46002 31.1504 6.46002 30.1004V15.3004C6.46002 14.2504 5.29986 13.4004 3.86673 13.4004C2.4336 13.4004 1.27344 14.2504 1.27344 15.3004V30.1004Z" fill="#FF800B" />
</g>
<defs>
<clipPath id="clip0_21262_3621">
<rect width="32" height="32" fill="white" transform="translate(0.119141)" />
</clipPath>
</defs>
</svg>
case 3:
return <svg width="33" height="32" viewBox="0 0 33 32" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ transform: 'scale(0.7)' }}>
<g clip-path="url(#clip0_21262_3625)">
<path d="M26.9316 30.1C26.9316 31.15 28.0918 32 29.5249 32C30.9581 32 32.1182 31.15 32.1182 30.1V1.9C32.1182 0.85 30.9581 0 29.5249 0C28.0918 0 26.9316 0.85 26.9316 1.9V30.1Z" fill="#02B188" />
<path d="M14.1035 30.1C14.1035 31.15 15.2637 32 16.6968 32C18.1299 32 19.2901 31.15 19.2901 30.1V8.9C19.2901 7.85 18.1299 7 16.6968 7C15.2637 7 14.1035 7.85 14.1035 8.9V30.1Z" fill="#02B188" />
<path d="M1.27344 30.1004C1.27344 31.1504 2.4336 32.0004 3.86673 32.0004C5.29986 32.0004 6.46002 31.1504 6.46002 30.1004V15.3004C6.46002 14.2504 5.29986 13.4004 3.86673 13.4004C2.4336 13.4004 1.27344 14.2504 1.27344 15.3004V30.1004Z" fill="#02B188" />
</g>
<defs>
<clipPath id="clip0_21262_3625">
<rect width="32" height="32" fill="white" transform="translate(0.119141)" />
</clipPath>
</defs>
</svg>
}
}
export default Meeting