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

1973 lines
83 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, CaretDownOutlined, CaretUpOutlined, CaretLeftOutlined, CaretRightOutlined } from '@ant-design/icons';
import { useLocation, useNavigate } from 'react-router-dom';
import { thumbImageBufferToBase64 } from '@/utils/package/base64'
import { storage } from '@/utils';
import { GetRoomUser, PostOpenMicr, PostOpenCamera, GetLeaveAll, PostRoomManager, DeleteRoomManager, GetRoomKickout, GetShowUser, PostShowUser, GetJoin, GetLeave, PostMuteAll, GetRoomUserItem, GetApplySpeak } from '@/api/Meeting';
import ImageUrl from '@/utils/package/ImageUrl'
import agora from '@/utils/package/agora'
import { onInvoke, onSignalr, offSignalr, onStart } from '@/utils/package/signalr';
import dayjs from 'dayjs';
import durationPlugin from 'dayjs/plugin/duration';
import { VideoSourceType } from 'agora-electron-sdk';
import Avatar from '@/components/Avatar';
import SharedFilesModel from '@/components/SharedFilesModel';
import StupWizard from '@/components/StupWizard';
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 [isClicked, setIsClicked] = useState(false);
const [statusList, setStatusList] = useState({
userList: false,
userChatList: false,
})
const [isSharedScreenModal, setIsSharedScreenModal] = 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([
[
{
title: '静音',
icon: ImageUrl.icon22,
iconActive: ImageUrl.icon22Active,
active: false,
},
{
title: '关闭视频',
icon: ImageUrl.icon23,
iconActive: ImageUrl.icon23Active,
active: false,
},
{
title: '申请发言',
icon: ImageUrl.icon47,
iconActive: ImageUrl.icon47Active,
active: false,
},
],
[
{
title: '共享屏幕',
icon: ImageUrl.icon24,
active: false,
},
{
title: '共享文件',
icon: ImageUrl.icon25,
active: false,
},
{
title: '邀请人员',
icon: ImageUrl.icon26,
active: false,
},
{
title: '会议监控',
icon: ImageUrl.icon48,
active: false,
},
{
title: '录制',
icon: ImageUrl.icon27,
iconActive: ImageUrl.icon27Active,
active: false,
},
{
title: '设置',
icon: ImageUrl.icon28,
active: false,
},
{
title: '结束',
icon: ImageUrl.icon29,
active: false,
},
],
[
{
title: '成员列表',
icon: ImageUrl.icon30,
active: false,
},
{
title: '聊天',
icon: ImageUrl.icon31,
active: false,
},
],
])
const [footerListIndex, setFooterListIndex] = useState<any>({
itemIndex: 0,
rowIndex: 0,
});
const [roomUserList, setRoomUserList] = useState<any>([])
const [chatList, setChatList] = useState<any>([])
const [currentVideoId, setCurrentVideoId] = useState('')
let [currentSeconds, setCurrentSeconds] = useState(0)
const [currentEffective, setCurrentEffective] = useState(0)
const [isComputerAudio, setIsComputerAudio] = useState(true)
const [isFluencyPriority, setIsFluencyPriority] = 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 [mediaStream, setMediaStream] = useState<any>('')
const [isShare, setIsShare] = useState<any>(null)
const [isSharePopConfirm, setIsSharePopConfirm] = useState<any>(false)
const [isShareUser, setIsShareUser] = useState<any>(null)
const [currentLookUserStatus, setCurrentLookUserStatus] = useState<0 | 1 | 2 | 3 | 4>(1)
const [clickCurrentLookUserStatus, setClickCurrentLookUserStatus] = useState<boolean>(true)
const [commonlyChatList] = useState<any>([
'能听到我说话吗?',
'听得到',
'听不到',
'我要发言',
])
const [roomUserItem, setRoomUserItem] = useState<any>(null)
const [isAdmin, setIsAdmin] = useState<number>(0)
const [api, contextHolder] = notification.useNotification({
stack: {
threshold: 3
}
});
const [isVideoFullScreen, setIsVideoFullScreen] = useState<boolean>(false)
let userInfo = JSON.parse(storage.getItem('user') as string)
const msgTips = '您不是管理员或发言人,无法开启此功能!'
useEffect(() => {
let time: NodeJS.Timeout;
setUser(userInfo)
setTimeout(() => {
if (location.hash.indexOf('/login') === -1) {
window.electron.getIsMaximized().then((res: boolean) => {
if (!res) {
window.electron.setViewStatus('maximize')
}
})
}
}, 1000)
setMeetingMode('StandardMode');
agora.init(true)
agora.registerEventHandler({
onJoinChannelSuccess: async (info: any, _elapsed: any) => {
if (String(info.localUid).length !== 9) {
await getJoin(state.enableMicr, state.enableCamera)
setTimeout(async () => {
await agora.setupLocalVideo({
uid: Number(info.localUid),
view: document.getElementById(`video-${info.localUid}`),
channelId: info.channelId,
sourceType: VideoSourceType.VideoSourceCameraPrimary,
})
getShowUser();
}, 1000);
}
},
onUserJoined: async (info: any, remoteUid: any, _elapsed: any) => {
if (String(remoteUid).length === 9) {
setIsShare(remoteUid)
} else {
setTimeout(async () => {
await agora.setupRemoteVideoJoin({
uid: Number(remoteUid),
view: document.getElementById(`video-${remoteUid}`),
channelId: info.channelId,
})
}, 1000);
}
},
onUserOffline: async (info: any, remoteUid: any, _reason: any) => {
if (String(remoteUid).length === 9) {
setIsShare(null)
renderVideo()
}
await agora.setupRemoteVideo({
uid: Number(remoteUid),
view: null,
channelId: info.channelId,
});
setCurrentVideoId((res: any) => {
if (Number(res) === remoteUid) {
getShowUser();
}
return res
})
},
onAudioVolumeIndication: async (speakers: any) => {
speakers.forEach((item: any) => {
let dom = document.getElementById(`micr-${item.uid ? item.uid : userInfo.uid}`);
if (dom) {
const percentage = (item.volume / 255) * 100
dom.style.width = `${percentage}%`
}
});
}
})
agora.startCameraCapture()
agora.setJoinChannel({
channelId: state.channelId,
uid: userInfo.uid,
token: state.token,
})
storage.setItem('noViewChatList', 0)
window.addEventListener('customStorageChange', handleCustomStorageChange);
window.addEventListener('online', handleNetworkChange);
window.addEventListener('offline', handleNetworkChange);
time = setInterval(() => {
setCurrentSeconds(currentSeconds++)
}, 1000)
return () => {
window.removeEventListener('customStorageChange', handleCustomStorageChange);
window.removeEventListener('online', handleNetworkChange);
window.removeEventListener('offline', handleNetworkChange);
clearInterval(time)
};
}, []);
useEffect(() => {
const connection = (navigator as any).connection
if (connection.downlink === 0) {
setCurrentEffective(0)
} else {
let effectiveTypeLength = ['slow-2g', '2g', '3g', '4g'].indexOf((navigator as any).connection.effectiveType)
setCurrentEffective(effectiveTypeLength + 1)
}
}, [(navigator as any).connection.effectiveType]);
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(() => {
onSignalr(async (item: any) => {
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])
setStatusList((res: any) => {
if (!res.userChatList) {
api.open({
message: item.userName + '说:',
description: item.message,
duration: 3,
showProgress: true,
});
}
return res
})
chatScrollBotton()
break;
// 扩展操作
case 'Operation':
// 4:屏幕共享
switch (item.type) {
case 4:
setIsShare((res: any) => {
if (userInfo.screenShareId === String(res)) {
changeStatusList({
title: '停止共享'
}, 1, 0)
}
return res
})
break;
}
break;
// 全员离开房间
case 'AllLeave':
leaveChannel(false)
break;
// 移出会议
case 'ForceExitRoom':
leaveChannel()
break;
// 更新视图模式
case 'RefreshView':
setMeetingMode(item.type)
break;
// 全员看他
case 'ShowUser':
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) {
message.success(`操作成功`)
await agora.updateChannelMediaOptions(item.user.isRoomManager)
await postOpenMicrApi(item.user.isRoomManager, userInfo.uid, false)
await postOpenCameraApi(item.user.isRoomManager, userInfo.uid)
await stopScreenCapture()
} else {
message.success(`${item.user.userName}已结束发言`)
}
} else {
if (item.user.uid === userInfo.uid) {
message.success(`管理员${item.user.isRoomManager ? '设置' : '取消'}您为发言人`)
await agora.updateChannelMediaOptions(item.user.isRoomManager)
await postOpenMicrApi(item.user.isRoomManager, userInfo.uid, false)
await postOpenCameraApi(item.user.isRoomManager, userInfo.uid)
await stopScreenCapture()
} else {
message.success(`管理员${item.user.isRoomManager ? '设置' : '取消'}${item.user.userName}为发言人`)
}
}
if (!item.user.isRoomManager) {
changeVideo()
}
})
break;
// 申请发言
case 'ApplyToSpeak':
api.open({
message: '',
description: <div>
<span style={{ fontSize: '16px' }}>{item.uname}</span>
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button
type="primary"
className='m-ant-btn'
onClick={async (e: any) => {
await PostRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: item.uid
})
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) => {
console.log(row.className);
if (row.className === 'ant-notification-notice-close') {
row.click()
}
})
}
})
}
}}
></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) => {
console.log(row.className);
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,
});
break;
}
})
return () => {
offSignalr()
}
}, [])
useEffect(() => {
if (recorder) {
recorder.start();
recorder.ondataavailable = (event: any) => {
const blob = new Blob([event.data], {
type: 'video/mp4',
});
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);
await fs.writeFile(`${setting.recordingFilesPath}${+new Date()}.mp4`, buffer, {});
message.success(`录制成功!文件已保存至:${setting.recordingFilesPath}`)
try {
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}"`);
}
} catch (error: any) {
if (error.code === 'ENOENT') {
message.error('文件夹不存在!')
} else {
message.error(error)
}
}
};
reader.readAsArrayBuffer(blob);
}
};
}, [recorder])
useEffect(() => {
let timer: NodeJS.Timeout;
if (isClicked) {
timer = setTimeout(() => {
setIsClicked(false);
}, 10000);
}
return () => clearTimeout(timer);
}, [isClicked]);
const changeAgoraDevice = () => {
setRoomUserList((res: any) => {
res.forEach(async (item: any) => {
if (item.roleId === '1') {
item.role = 'admin'
} else if (item.isRoomManager) {
item.role = 'speaker'
} else {
item.role = 'user'
}
if (item.uid === userInfo.uid) {
const footerListTemplate = [...footerList]
await agora.getVideoDeviceManager().then(async (res) => {
if (res.list.length) {
footerListTemplate[0][1].title = item.enableCamera ? '关闭视频' : '开启视频'
footerListTemplate[0][1].active = !item.enableCamera
await agora.muteLocalVideoStream(!item.enableCamera)
} else {
footerListTemplate[0][1].title = '开启视频'
footerListTemplate[0][1].active = true
await agora.muteLocalVideoStream(true)
}
})
await agora.getAudioMediaList().then(async (res) => {
if (res.ecordingList.length) {
footerListTemplate[0][0].title = item.enableMicr ? '静音' : '解除静音'
footerListTemplate[0][0].active = !item.enableMicr
await agora.muteLocalAudioStream(!item.enableMicr)
} else {
footerListTemplate[0][0].title = '解除静音'
footerListTemplate[0][0].active = true
await agora.muteLocalAudioStream(true)
}
})
if (userInfo.roleId !== '1') {
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) => (item.roleId === '1' || 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,
})
} else {
await agora.setupRemoteVideoJoin({
uid: Number(userItem.uid),
view: document.getElementById(`video-${userItem.uid}`),
channelId: state.channelId,
})
}
}
// 替换数据
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 key in item.user) {
userItem[key] = item.user[key];
}
userItem.isAdmin = item.user.roleId === '1' || item.user.isRoomManager;
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 = item.user.roleId === '1' || item.user.isRoomManager;
refreshVideoView(userItem)
changeVideo()
return [...res]
} else {
item.user.isRoom = true;
item.user.isAdmin = item.user.roleId === '1' || item.user.isRoomManager;
refreshVideoView(item.user)
changeVideo()
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
})
break;
case 'OperAllMicr':
setRoomUserList((res: any) => {
res.forEach((row: any) => {
if (row.uid !== item.uid) {
row.enableMicr = item.enableMicr
}
})
return res
})
break;
}
changeAgoraDevice()
}
// 修改当前观看用户
const changeVideo = (): void => {
setRoomUserList((list: any) => {
let row = list.filter((i: any) => i.roleId === '1' || i.isRoomManager)
setCurrentLookUserAccount((res: any) => {
let has = row.find((x: any) => x.uid === res.uid)
if (!has && row.length) {
renderVideo(row[0].uid)
}
return res
})
return list
})
}
// 网络
const handleNetworkChange = (): void => {
if (navigator.onLine) {
message.success('网络已恢复。')
setTimeout(async () => {
await onStart(async () => {
await getJoin(!footerList[0][0].active, !footerList[0][1].active)
})
}, 1000)
} else {
message.error('网络已断开!')
}
}
// 渲染视频
const renderVideo = async (uid: string = ''): Promise<void> => {
if (uid) {
if (currentVideoId === uid || clickCurrentLookUserStatus === false) {
return
}
} else {
uid = userInfo.uid
}
setClickCurrentLookUserStatus(false)
setCurrentLookUserStatus(0)
setRoomUserList((res: any) => {
let item = res.find((item: any) => item.uid === uid || item.screenShareId === uid)
if (item) {
setCurrentVideoId(item.uid)
}
return res
})
setTimeout(() => {
if (uid === userInfo.uid || uid === userInfo.screenShareId) {
if (String(uid).length === 9) {
// 共享屏幕
setCurrentLookUserStatus(2)
setTimeout(async () => {
await agora.setupLocalVideo({
uid: Number(uid),
view: document.getElementById(`video-source-screen`) as HTMLElement,
channelId: state.channelId,
sourceType: VideoSourceType.VideoSourceScreen,
})
setClickCurrentLookUserStatus(true)
}, 1000);
} else {
// 摄像头
setCurrentLookUserStatus(1)
setTimeout(async () => {
await agora.setupLocalVideo({
uid: Number(uid),
view: document.getElementById(`video-source-camera-primary`) as HTMLElement,
channelId: state.channelId,
sourceType: VideoSourceType.VideoSourceCameraPrimary,
})
setClickCurrentLookUserStatus(true)
}, 1000);
}
} else {
if (String(uid).length === 9) {
// 共享屏幕
setCurrentLookUserStatus(3)
setTimeout(async () => {
await agora.setupRemoteVideoJoin({
uid: Number(uid),
view: document.getElementById(`video-source-remote-screen`) as HTMLElement,
channelId: state.channelId,
})
setClickCurrentLookUserStatus(true)
}, 1000);
} else {
// 摄像头
setCurrentLookUserStatus(4)
setTimeout(async () => {
await agora.setupRemoteVideoJoin({
uid: Number(uid),
view: document.getElementById(`video-source-remote-camera`) as HTMLElement,
channelId: state.channelId,
})
setClickCurrentLookUserStatus(true)
}, 1000);
}
}
}, 1000)
}
// 全员观看
const getShowUser = async (): Promise<void> => {
await GetShowUser(state.channelId).then(async (res) => {
if (res.code === 200 && res.data) {
renderVideo(res.data)
changeVideo()
}
})
}
// 加入房间时间
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 changeStatusList = async (row: any, itemIndex: number, rowIndex: number): Promise<void> => {
const footerListTemplate = [...footerList]
setFooterListIndex({
itemIndex,
rowIndex,
})
switch (row.title) {
case '成员列表':
setStatusList({
userList: statusList.userList ? false : true,
userChatList: false,
})
storage.setItem('noViewChatList', 0)
setNoViewChatList(0)
break;
case '聊天':
setStatusList({
userList: false,
userChatList: statusList.userChatList ? false : true,
})
storage.setItem('noViewChatList', 0)
setNoViewChatList(0)
chatScrollBotton()
break;
case '共享屏幕':
await getUserRoomInfo().then(async (res) => {
if (res) {
getDesktopCapturerVideo()
setIsSharedScreenModal(true)
} else {
message.error(msgTips)
}
})
break;
case '停止共享':
await getUserRoomInfo().then(async (res) => {
if (res) {
await stopScreenCapture()
} else {
message.error(msgTips)
}
})
break;
case '静音':
await postOpenMicr(false, user.uid)
break;
case '解除静音':
await postOpenMicr(true, user.uid)
break;
case '关闭视频':
await postOpenCamera(false, user.uid)
break;
case '开启视频':
await postOpenCamera(true, user.uid)
break;
case '设置':
stupWizardRef.current.changeModal()
break;
case '邀请人员':
await getUserRoomInfo().then(async (res) => {
if (res) {
invitingPersonnelRef.current.changeInvitingPersonnelModal()
} else {
message.error(msgTips)
}
})
break;
case '录制':
window.electron.getSources().then((sources: any) => {
const screenId = sources[0].id;
navigator.mediaDevices.getUserMedia({
audio: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: screenId,
}
} as any,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: screenId,
}
} as any
}).then(async (steam) => {
try {
const audioTracks = await navigator.mediaDevices
.getUserMedia({ audio: true, video: false })
.then(audioStream => audioStream.getAudioTracks()[0]);
steam.addTrack(audioTracks);
} catch (error) {
}
setMediaStream(steam)
setRecorder(new MediaRecorder(steam))
})
})
const setting = await JSON.parse(storage.getItem('setting') as string)
try {
await fs.access(setting.recordingFilesPath, fs.constants.F_OK)
footerListTemplate[itemIndex][rowIndex].title = '录制中'
footerListTemplate[itemIndex][rowIndex].active = true
setFooterList(footerListTemplate)
} catch (error: any) {
if (error.code === 'ENOENT') {
message.error('文件夹不存在!')
return
} else {
message.error(error)
}
}
break;
case '录制中':
footerListTemplate[itemIndex][rowIndex].title = '录制'
footerListTemplate[itemIndex][rowIndex].active = false
setFooterList(footerListTemplate)
stopRecorderMedia()
break;
case '共享文件':
sharedFilesModelRef.current.getData()
break;
case '申请发言':
if (!isClicked) {
setIsClicked(true);
GetApplySpeak(state.channelId).then(res => {
if (res.code === 200) {
message.success('申请发言成功')
}
})
} else {
message.error('申请已提交,请勿重复点击!');
}
break;
case '会议监控':
window.electron.oepnWindow({
url: location.origin + '/#/userVideo'
})
break;
}
}
// 停止录制
const stopRecorderMedia = async (): Promise<void> => {
if (recorder) {
await recorder.stop();
}
if (mediaStream) {
await mediaStream.getTracks().forEach((track: any) => {
track.stop()
});
}
setRecorder('')
setMediaStream('')
}
// 退出房间
const leaveChannel = async (bool: boolean = true): Promise<void> => {
await stopRecorderMedia()
if (bool) {
await getLeave()
}
agora.leaveChannel()
navigate('/home/index')
}
// 分享屏幕
const clickSharedScreen = async (): Promise<void> => {
let data = sharedScreenList.find((item: any) => item.sourceId === sharedScreenItem.sourceId)
if (data) {
const footerListTemplate = [...footerList]
footerListTemplate[footerListIndex.itemIndex][footerListIndex.rowIndex].title = '停止共享'
setIsSharedScreenModal(false)
await agora.setDesktopCapturerVideo(sharedScreenItem, isComputerAudio, isFluencyPriority)
await allUserLook(user.screenShareId, user.userName)
} else {
message.error('请选择应用!')
}
}
// 获取桌面可共享屏幕的引用
const getDesktopCapturerVideo = (): void => {
agora.getDesktopCapturerVideo().then((res: any) => {
if (sharedScreenList.length !== res.length) {
res.forEach((item: any) => {
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 stopScreenCapture = async (): Promise<void> => {
const footerListTemplate = [...footerList]
await agora.leaveChannelEx(userInfo.screenShareId)
agora.stopScreenCapture()
footerListTemplate[1][0].title = '共享屏幕'
setFooterList(footerListTemplate)
renderVideo()
}
// 获取房间用户
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 = item.roleId === '1' || 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 'reconnect':
if (e.value == true) {
storage.setItem('reconnect', false)
await getJoin(!footerList[0][0].active, !footerList[0][1].active)
}
break;
}
};
// 聊天发送
const sendMsg = (text?: string): void => {
let msg = text ? text : textMsg;
if (msg) {
onInvoke('sendChannelMsg', {
roomNum: state.channelId,
msg: msg,
})
setChatList((newChatList: any) => [...newChatList, {
uid: user.uid,
userName: user.userName,
message: msg,
timestamp: +new Date()
}])
setTextMsg('');
chatScrollBotton()
} else {
message.error('请输入内容!')
}
}
// 聊天框滚动到底部
const chatScrollBotton = (): void => {
setTimeout(() => {
const meetingUserChatContentView = document.getElementById('meetingUserChatContentView') as HTMLElement;
if (meetingUserChatContentView) {
meetingUserChatContentView.scrollTop = meetingUserChatContentView.scrollHeight;
}
}, 0);
}
// 开关麦克风
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 && (userItem.roleId === '1' || 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 <CaretDownOutlined className={`${styles.standardModeIcon} drag`} style={{ top: '-10px' }} title='展开' onClick={() => setIsVideoFullScreen(false)} /> //下
} else {
return <CaretUpOutlined className={`${styles.standardModeIcon} drag`} style={{ top: '148px' }} title='收起' onClick={() => setIsVideoFullScreen(true)} /> //上
}
case 'SpeakerMode':
if (isVideoFullScreen) {
return <CaretRightOutlined className={`${styles.speakerModeIcon} drag`} style={{ left: '-10px' }} title='展开' onClick={() => setIsVideoFullScreen(false)} /> //右
} else {
return <CaretLeftOutlined className={`${styles.speakerModeIcon} drag`} style={{ left: 'calc(18% - 12px)' }} title='收起' onClick={() => setIsVideoFullScreen(true)} /> //左
}
}
}
// 加入房间
const getJoin = async (enableMicr: boolean, enableCamera: boolean): Promise<void> => {
await GetJoin({
roomNum: state.channelId,
enableMicr,
enableCamera
})
await getRoomUser()
}
// 离开房间
const getLeave = async (): Promise<void> => {
await GetLeave({
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}>
{contextHolder}
<div className={styles.meetingHeader}>
<div>
<div>
{networkIcon(currentEffective)}
</div>
<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'>
{roomUserList.map((item: any, index: number) => {
return (index <= 19 && item.isRoom && item.isAdmin ? <div
id={item.uid}
className={`${styles.meetingContentSwiperCard}`}
key={index}
onClick={() => {
if (String(isShare) === item.screenShareId) {
renderVideo(item.screenShareId)
} else {
renderVideo(item.uid)
}
}}
>
<div className={`${styles.meetingContentSwiperCardVdeio} ${(item.uid === currentVideoId) && !isVideoFullScreen ? styles.active : ''} ${(item.roleId === '1' || 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}
{user.roleId === '1' ? <Popover placement="bottom" title={''} content={
<div className={styles.meetingContentSwiperCardPopover}>
{item.isRoomManager || item.roleId === '1' ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={(event) => {
event.stopPropagation();
setAllUserLook(item)
}}
>Ta</Button> : null}
{item.uid !== user.uid && item.roleId !== '1' ? <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"
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 className={styles.meetingContentSwiperCardBorder}>
{item.enableMicr ? <div id={`micr-${item.uid}`}></div> : null}
</div>
</div> : null)
}
)}
{currentLookUserStatus === 0 && currentLookUserAccount ?
<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>
{meetingContentUser(currentLookUserAccount, true)}
</div> : null}
{currentLookUserStatus === 1 && currentLookUserAccount ?
<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>
{meetingContentUser(currentLookUserAccount, true)}
{currentLookUserAccount.enableCamera ? null : meetingContentError(currentLookUserAccount)}
</div> : null}
{currentLookUserStatus === 2 && currentLookUserAccount ?
<div className={`${styles.meetingContentSwiperCard} ${setMeetingContentSwiperCardClass()} ${setMeetingContentSwiperCardFullScreenClass()}`}>
<div className={`${styles.meetingContentSwiperCardVdeio}`} id='video-source-screen'>
<div className={styles.meetingContentSwiperCardVdeioLoading}>
<Avatar name={currentLookUserAccount.userName} />
</div>
</div>
{meetingContentUser(currentLookUserAccount, true)}
</div> : null}
{currentLookUserStatus === 3 && currentLookUserAccount ?
<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>
{meetingContentUser(currentLookUserAccount, true)}
</div> : null}
{currentLookUserStatus === 4 && currentLookUserAccount ?
<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>
{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) ? (
<div className={styles.meetingContentBodyRight}>
{statusList.userList ?
<div className={styles.meetingUserList}>
<div className={styles.meetingUserListTitle}>
<span></span>
<img src={ImageUrl.icon18} alt="" className='drag' onClick={() => {
setStatusList({
userList: false,
userChatList: false,
})
}} />
</div>
<div style={{ padding: '0 14px', boxSizing: 'border-box' }}>
<Input
placeholder="请输入用户名"
className='drag'
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} className='drag'>
<div>
<div><Avatar name={item.userName} /></div>
<span>
{item.userName}{item.uid === user.uid ? '(我)' : ''}
{item.roleId === '1' || item.isRoomManager ?
<span style={{ color: '#02B188', marginLeft: '4px' }}>
{item.roleId === '1' ? '管理员' : '发言人'}
</span>
: null}
</span>
</div>
<div>
{item.roleId === '1' || item.isRoomManager ? <div>
<img src={item.enableMicr ? ImageUrl.icon22 : ImageUrl.icon22Active} alt="" onClick={() => {
postOpenMicr(!item.enableMicr, item.uid)
}} title={item.enableMicr ? '静音' : '解除声音'} />
</div> : null}
{item.roleId === '1' || 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 && user.roleId === '1' ? <div>
<Popover placement="left" title={''} content={
<div className='drag'>
{item.roleId !== '1' ? <Button
type="primary"
className='m-ant-btn'
style={{ marginBottom: '10px', width: '100%' }}
size={'small'}
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 ? '取消发言人' : '设为发言人'}</Button> : null}
<Button
type="primary"
style={{ backgroundColor: '#EC3C3C', width: '100%' }}
size={'small'}
onClick={() => {
getRoomKickout(state.channelId, item.uid, item.userName)
}}
></Button>
</div>
}>
<EllipsisOutlined style={{
color: '#fff',
fontSize: '20px'
}} />
</Popover>
</div> : null}
</div>
</div> : null
)
}
)}
</div>
<div className={`${styles.meetingUserListFooter} drag`}>
<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>
:
<div className={styles.meetingUserChat} id='meetingUserChat'>
<div className={styles.meetingUserChatTitle}>
<span></span>
<img src={ImageUrl.icon18} alt="" className='drag' onClick={() => {
setStatusList({
userList: false,
userChatList: 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} drag`}>
{user.roleId === '1' ? <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 || roomUserItem.roleId === '1' ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={(event) => {
event.stopPropagation();
setAllUserLook(roomUserItem)
}}
>Ta</Button> : null}
{roomUserItem.uid !== user.uid && roomUserItem.roleId !== '1' ? <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"
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} drag`}>
{
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} drag`}>
<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>
) : null
}
</div>
<div className={styles.meetingContentFooter}>
{footerList.map((item, itemIndex) => {
return (
<div key={itemIndex}>
{item.map((row, rowIndex) => {
switch (row.title) {
case '结束':
return <Popover key={rowIndex}
content={
<div className='meetingContentFooterPopover'>
{user.roleId === '1' ?
<Popconfirm
title="提示"
description={`结束会议后,所有人将退出,是否结束?`}
onConfirm={async () => {
GetLeaveAll({
roomNum: state.channelId,
})
}}
onCancel={() => {
}}
okText="结束"
cancelText="取消"
>
<div></div>
</Popconfirm>
: null}
<div onClick={() => leaveChannel()}></div>
<div onClick={() => { setOpen(false) }}></div>
</div>
}
title=""
trigger="click"
open={open}
onOpenChange={() => setOpen(true)}
>
<div className='drag'>
<img src={row.active ? row.iconActive : row.icon} alt="" />
<span>{row.title}</span>
</div>
</Popover>
case '申请发言':
if (user.roleId !== '1') {
return <div className='drag' onClick={() => changeStatusList(row, itemIndex, rowIndex)} key={rowIndex}>
<img src={row.active ? row.iconActive : row.icon} alt="" />
<span>{row.title}</span>
</div>
}
return null
case '结束发言':
if (user.roleId !== '1') {
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 (user.roleId === '1') {
return <div className='drag' onClick={() => changeStatusList(row, itemIndex, rowIndex)} key={rowIndex}>
<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)} key={rowIndex}>
<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}
{row.title === '聊天' && noViewChatList > 0 ? <div
style={{
backgroundColor: 'red',
borderRadius: '50%',
}}
>{noViewChatList}</div> : null}
</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 ? <Popconfirm
title="提示"
description={`这将停止[${isShareUser?.userName}]的共享,是否继续?`}
open={isSharePopConfirm}
onConfirm={async () => {
setIsSharePopConfirm(false)
await onInvoke('sendOper', {
roomNum: state.channelId,
type: 4,
})
clickSharedScreen()
}}
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={() => {
clickSharedScreen()
}}></Button>
}
</div>
</div>
</div>
</Modal>
<SharedFilesModel ref={sharedFilesModelRef} />
<SpeakerModeModal ref={speakerModeModalRef} />
<InvitingPersonnelModal ref={invitingPersonnelRef} />
<StupWizard ref={stupWizardRef} />
</>
)
}
const meetingContentUser = (item: any, bool?: boolean) => {
return (
<>
<div className={styles.meetingContentUser}>
<div className={styles.meetingContentUserName}>
{item.roleId === '1' || item.isRoomManager ?
<div style={{ background: item.roleId === '1' ? '#FDC229' : '#3F51B5' }}>
<img src={ImageUrl.icon32} alt="" />
</div> : null}
{!item.enableMicr ? <img src={item.enableMicr ? ImageUrl.icon22 : ImageUrl.icon22Active} alt="" /> : ''}
<span style={{ maxWidth: bool ? '' : '8vw' }} title={`${item.userName}${item.roleId === '1' || item.isRoomManager ? item.roleId === '1' ? '(管理员)' : '(发言人)' : ''}`}>
{item.userName}
{item.roleId === '1' || item.isRoomManager ? item.roleId === '1' ? '(管理员)' : '(发言人)' : ''}
</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="16" height="16" viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_5499_5160)">
<path d="M1.9 32.8398C0.85 32.8398 0 31.9898 0 30.9398V23.3398C0 22.2898 0.85 21.4398 1.9 21.4398C2.95 21.4398 3.8 22.2898 3.8 23.3398V30.9398C3.8 31.9898 2.95 32.8398 1.9 32.8398ZM11.3 32.8398C10.25 32.8398 9.4 31.9898 9.4 30.9398V16.1398C9.4 15.0898 10.25 14.2398 11.3 14.2398C12.35 14.2398 13.2 15.0898 13.2 16.1398V30.9398C13.2 31.9898 12.35 32.8398 11.3 32.8398ZM20.7 32.8398C19.65 32.8398 18.8 31.9898 18.8 30.9398V9.73984C18.8 8.68984 19.65 7.83984 20.7 7.83984C21.75 7.83984 22.6 8.68984 22.6 9.73984V30.9398C22.6 31.9898 21.75 32.8398 20.7 32.8398ZM30.1 32.8398C29.05 32.8398 28.2 31.9898 28.2 30.9398V2.73984C28.2 1.68984 29.05 0.839844 30.1 0.839844C31.15 0.839844 32 1.68984 32 2.73984V30.9398C32 31.9898 31.15 32.8398 30.1 32.8398Z" fill="#7C8280" />
<path d="M8.86265 3.02995L6.10877 5.78383L3.35489 3.02995C2.99725 2.67231 2.40713 2.67231 2.04948 3.02995C1.69184 3.3876 1.69184 3.97772 2.04948 4.33536L4.80336 7.08924L2.04948 9.84312C1.69184 10.2008 1.69184 10.7909 2.04948 11.1485C2.40713 11.5062 2.99725 11.5062 3.35489 11.1485L6.10877 8.39465L8.86265 11.1485C9.22029 11.5062 9.81041 11.5062 10.1681 11.1485C10.5257 10.7909 10.5257 10.2008 10.1681 9.84312L7.41418 7.08924L10.1681 4.33536C10.5257 3.97772 10.5257 3.3876 10.1681 3.02995C9.81041 2.67231 9.22029 2.67231 8.86265 3.02995Z" fill="#A5A5A5" />
</g>
<defs>
<clipPath id="clip0_5499_5160">
<rect width="32" height="32" fill="white" transform="translate(0 0.839844)" />
</clipPath>
</defs>
</svg>
case 1:
return <svg width="16" height="16" viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.9 32.8385C0.85 32.8385 0 31.9885 0 30.9385V23.3385C0 22.2885 0.85 21.4385 1.9 21.4385C2.95 21.4385 3.8 22.2885 3.8 23.3385V30.9385C3.8 31.9885 2.95 32.8385 1.9 32.8385Z" fill="#02B188" />
<path d="M28.1992 30.9389C28.1992 31.9889 29.0492 32.8389 30.0992 32.8389C31.1492 32.8389 31.9992 31.9889 31.9992 30.9389V2.73887C31.9992 1.68887 31.1492 0.838867 30.0992 0.838867C29.0492 0.838867 28.1992 1.68887 28.1992 2.73887V30.9389Z" fill="#7C8280" />
<path d="M18.8008 30.9389C18.8008 31.9889 19.6508 32.8389 20.7008 32.8389C21.7508 32.8389 22.6008 31.9889 22.6008 30.9389V9.73887C22.6008 8.68887 21.7508 7.83887 20.7008 7.83887C19.6508 7.83887 18.8008 8.68887 18.8008 9.73887V30.9389Z" fill="#7C8280" />
<path d="M9.40039 30.9393C9.40039 31.9893 10.2504 32.8393 11.3004 32.8393C12.3504 32.8393 13.2004 31.9893 13.2004 30.9393V16.1393C13.2004 15.0893 12.3504 14.2393 11.3004 14.2393C10.2504 14.2393 9.40039 15.0893 9.40039 16.1393V30.9393Z" fill="#7C8280" />
</svg>
case 2:
return <svg width="16" height="16" viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.9 32.8385C0.85 32.8385 0 31.9885 0 30.9385V23.3385C0 22.2885 0.85 21.4385 1.9 21.4385C2.95 21.4385 3.8 22.2885 3.8 23.3385V30.9385C3.8 31.9885 2.95 32.8385 1.9 32.8385Z" fill="#02B188" />
<path d="M28.1992 30.9389C28.1992 31.9889 29.0492 32.8389 30.0992 32.8389C31.1492 32.8389 31.9992 31.9889 31.9992 30.9389V2.73887C31.9992 1.68887 31.1492 0.838867 30.0992 0.838867C29.0492 0.838867 28.1992 1.68887 28.1992 2.73887V30.9389Z" fill="#7C8280" />
<path d="M18.8008 30.9389C18.8008 31.9889 19.6508 32.8389 20.7008 32.8389C21.7508 32.8389 22.6008 31.9889 22.6008 30.9389V9.73887C22.6008 8.68887 21.7508 7.83887 20.7008 7.83887C19.6508 7.83887 18.8008 8.68887 18.8008 9.73887V30.9389Z" fill="#7C8280" />
<path d="M9.40039 30.9393C9.40039 31.9893 10.2504 32.8393 11.3004 32.8393C12.3504 32.8393 13.2004 31.9893 13.2004 30.9393V16.1393C13.2004 15.0893 12.3504 14.2393 11.3004 14.2393C10.2504 14.2393 9.40039 15.0893 9.40039 16.1393V30.9393Z" fill="#02B188" />
</svg>
case 3:
return <svg width="16" height="16" viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.9 32.8385C0.85 32.8385 0 31.9885 0 30.9385V23.3385C0 22.2885 0.85 21.4385 1.9 21.4385C2.95 21.4385 3.8 22.2885 3.8 23.3385V30.9385C3.8 31.9885 2.95 32.8385 1.9 32.8385Z" fill="#02B188" />
<path d="M28.1992 30.9389C28.1992 31.9889 29.0492 32.8389 30.0992 32.8389C31.1492 32.8389 31.9992 31.9889 31.9992 30.9389V2.73887C31.9992 1.68887 31.1492 0.838867 30.0992 0.838867C29.0492 0.838867 28.1992 1.68887 28.1992 2.73887V30.9389Z" fill="#7C8280" />
<path d="M18.8008 30.9389C18.8008 31.9889 19.6508 32.8389 20.7008 32.8389C21.7508 32.8389 22.6008 31.9889 22.6008 30.9389V9.73887C22.6008 8.68887 21.7508 7.83887 20.7008 7.83887C19.6508 7.83887 18.8008 8.68887 18.8008 9.73887V30.9389Z" fill="#02B188" />
<path d="M9.40039 30.9393C9.40039 31.9893 10.2504 32.8393 11.3004 32.8393C12.3504 32.8393 13.2004 31.9893 13.2004 30.9393V16.1393C13.2004 15.0893 12.3504 14.2393 11.3004 14.2393C10.2504 14.2393 9.40039 15.0893 9.40039 16.1393V30.9393Z" fill="#02B188" />
</svg>
case 4:
return <svg width="16" height="16" viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.9 32.8385C0.85 32.8385 0 31.9885 0 30.9385V23.3385C0 22.2885 0.85 21.4385 1.9 21.4385C2.95 21.4385 3.8 22.2885 3.8 23.3385V30.9385C3.8 31.9885 2.95 32.8385 1.9 32.8385Z" fill="#02B188" />
<path d="M28.1992 30.9389C28.1992 31.9889 29.0492 32.8389 30.0992 32.8389C31.1492 32.8389 31.9992 31.9889 31.9992 30.9389V2.73887C31.9992 1.68887 31.1492 0.838867 30.0992 0.838867C29.0492 0.838867 28.1992 1.68887 28.1992 2.73887V30.9389Z" fill="#02B188" />
<path d="M18.8008 30.9389C18.8008 31.9889 19.6508 32.8389 20.7008 32.8389C21.7508 32.8389 22.6008 31.9889 22.6008 30.9389V9.73887C22.6008 8.68887 21.7508 7.83887 20.7008 7.83887C19.6508 7.83887 18.8008 8.68887 18.8008 9.73887V30.9389Z" fill="#02B188" />
<path d="M9.40039 30.9393C9.40039 31.9893 10.2504 32.8393 11.3004 32.8393C12.3504 32.8393 13.2004 31.9893 13.2004 30.9393V16.1393C13.2004 15.0893 12.3504 14.2393 11.3004 14.2393C10.2504 14.2393 9.40039 15.0893 9.40039 16.1393V30.9393Z" fill="#02B188" />
</svg>
}
}
export default Meeting