1557 lines
65 KiB
TypeScript
1557 lines
65 KiB
TypeScript
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 } from "antd";
|
||
import { SearchOutlined, EllipsisOutlined } 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 } 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 { 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 [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.icon24,
|
||
active: false,
|
||
},
|
||
{
|
||
title: '共享文件',
|
||
icon: ImageUrl.icon25,
|
||
active: false,
|
||
},
|
||
{
|
||
title: '邀请人员',
|
||
icon: ImageUrl.icon26,
|
||
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>([
|
||
'能听到我说话吗?',
|
||
'听得到',
|
||
'听不到',
|
||
'我要发言',
|
||
])
|
||
let userInfo = JSON.parse(storage.getItem('user') as string)
|
||
const msgTips = '您不是管理员或发言人,无法开启此功能!'
|
||
useEffect(() => {
|
||
let time = null as any;
|
||
setUser(userInfo)
|
||
setTimeout(() => {
|
||
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)
|
||
}
|
||
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])
|
||
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(false)
|
||
break;
|
||
// 更新视图模式
|
||
case 'RefreshView':
|
||
setMeetingMode(item.type)
|
||
break;
|
||
// 全员看他
|
||
case 'ShowUser':
|
||
getShowUser()
|
||
break;
|
||
// 用户加入频道回调
|
||
case 'UserJoined':
|
||
setAllUserListData('UserJoined', item)
|
||
break;
|
||
// 用户退出频道回调
|
||
case 'UserLeave':
|
||
setAllUserListData('UserLeave', item)
|
||
break;
|
||
// 所有用户开闭麦
|
||
case 'OperAllMicr':
|
||
setAllUserListData('OperAllMicr', item)
|
||
break;
|
||
// 用户关闭开启麦克风
|
||
case 'OperMicr':
|
||
setAllUserListData('OperMicr', item)
|
||
break;
|
||
// 用户开启关闭摄像头
|
||
case 'OperCamera':
|
||
setAllUserListData('OperCamera', item)
|
||
break;
|
||
// 发言人用户信息刷新
|
||
case 'ManagerRefresh':
|
||
if (item.user.uid === userInfo.uid) {
|
||
await agora.updateChannelMediaOptions(item.user.isRoomManager)
|
||
if (!item.user.isRoomManager) {
|
||
await postOpenMicrApi(false, userInfo.uid)
|
||
await postOpenCameraApi(false, userInfo.uid)
|
||
}
|
||
}
|
||
setAllUserListData('ManagerRefresh', item)
|
||
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])
|
||
|
||
const changeAgoraDevice = () => {
|
||
function sortUsersByRole(arr: any[]): any[] {
|
||
// 使用 sort 方法对数组进行排序
|
||
return arr.sort((a: any, b: any) => {
|
||
// 定义角色优先级
|
||
const rolePriority: any = {
|
||
admin: 1,
|
||
speaker: 2,
|
||
user: 3,
|
||
};
|
||
// 获取角色优先级
|
||
const aPriority = rolePriority[a.role] || 3; // 默认为普通用户
|
||
const bPriority = rolePriority[b.role] || 3; // 默认为普通用户
|
||
// 比较角色优先级
|
||
if (aPriority < bPriority) {
|
||
return -1; // a 的优先级更高
|
||
} else if (aPriority > bPriority) {
|
||
return 1; // b 的优先级更高
|
||
} else {
|
||
// 如果角色相同,则可以按其他标准排序,例如按姓名字母顺序
|
||
return a.userName.localeCompare(b.userName);
|
||
}
|
||
});
|
||
}
|
||
setRoomUserList((res: any) => {
|
||
res.forEach((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]
|
||
footerListTemplate[0][0].title = item.enableMicr ? '静音' : '解除静音'
|
||
footerListTemplate[0][0].active = !item.enableMicr
|
||
footerListTemplate[0][1].title = item.enableCamera ? '关闭视频' : '开启视频'
|
||
footerListTemplate[0][1].active = !item.enableCamera
|
||
setFooterList(footerListTemplate)
|
||
agora.muteLocalAudioStream(!item.enableMicr)
|
||
agora.muteLocalVideoStream(!item.enableCamera)
|
||
}
|
||
if (userSearchValue) {
|
||
if (item.userName.indexOf(userSearchValue) !== -1) {
|
||
item.isShow = true;
|
||
} else {
|
||
item.isShow = false;
|
||
}
|
||
} else {
|
||
item.isShow = true;
|
||
}
|
||
});
|
||
return sortUsersByRole(res)
|
||
})
|
||
}
|
||
// 替换数据
|
||
const setAllUserListData = (key: string, item: any): 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];
|
||
}
|
||
}
|
||
setRoomUserList(res)
|
||
return res
|
||
})
|
||
break;
|
||
case 'UserJoined':
|
||
setRoomUserList((res: any) => [...res, item.user])
|
||
break;
|
||
case 'UserLeave':
|
||
setRoomUserList((res: any) => {
|
||
let userItemIndex = res.findIndex((row: any) => row.uid === item.uid)
|
||
res.splice(userItemIndex, 1)
|
||
return res
|
||
})
|
||
break;
|
||
case 'OperAllMicr':
|
||
setRoomUserList((res: any) => {
|
||
res.forEach((row: any) => {
|
||
if (row.uid !== item.uid) {
|
||
row.enableMicr = item.enableMicr
|
||
}
|
||
})
|
||
setRoomUserList(res)
|
||
return res
|
||
})
|
||
break;
|
||
}
|
||
changeAgoraDevice()
|
||
}
|
||
// 网络
|
||
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 (currentVideoId === uid || clickCurrentLookUserStatus === false) {
|
||
return
|
||
}
|
||
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-camera`) 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-screen`) as HTMLElement,
|
||
channelId: state.channelId,
|
||
})
|
||
setClickCurrentLookUserStatus(true)
|
||
}, 1000);
|
||
}
|
||
}
|
||
}, 500)
|
||
}
|
||
// 全员观看
|
||
const getShowUser = async (): Promise<void> => {
|
||
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 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)
|
||
break;
|
||
case '共享屏幕':
|
||
if (getUserRoomInfo()) {
|
||
getDesktopCapturerVideo()
|
||
setIsSharedScreenModal(true)
|
||
} else {
|
||
message.error(msgTips)
|
||
}
|
||
break;
|
||
case '停止共享':
|
||
if (getUserRoomInfo()) {
|
||
await agora.leaveChannelEx(userInfo.screenShareId)
|
||
agora.stopScreenCapture()
|
||
await allUserLook(userInfo.uid)
|
||
footerListTemplate[itemIndex][rowIndex].title = '共享屏幕'
|
||
} 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 '邀请人员':
|
||
invitingPersonnelRef.current.changeInvitingPersonnelModal()
|
||
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;
|
||
}
|
||
}
|
||
// 停止录制
|
||
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)
|
||
} 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): Promise<void> => {
|
||
await PostShowUser(state.channelId, uid).then(res => {
|
||
if (res.code === 200) {
|
||
getShowUser()
|
||
}
|
||
})
|
||
}
|
||
|
||
// 获取房间用户
|
||
const getRoomUser = async (): Promise<void> => {
|
||
GetRoomUser(state.channelId).then(res => {
|
||
if (res.code === 200) {
|
||
res.data.forEach((item: any) => {
|
||
item.isShow = true;
|
||
})
|
||
setRoomUserList(res.data)
|
||
changeAgoraDevice()
|
||
agora.updateChannelMediaOptions(getUserRoomInfo(res.data) ? true : false)
|
||
}
|
||
})
|
||
}
|
||
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: state.uid,
|
||
userName: user.userName,
|
||
message: msg,
|
||
}])
|
||
setTextMsg('')
|
||
} else {
|
||
message.error('请输入内容!')
|
||
}
|
||
}
|
||
// 开关麦克风
|
||
const postOpenMicr = async (enableMicr: boolean, uid: string, isAll?: boolean): Promise<void> => {
|
||
if (getUserRoomInfo()) {
|
||
postOpenMicrApi(enableMicr, uid, isAll)
|
||
} else {
|
||
message.error(msgTips)
|
||
}
|
||
}
|
||
// 开关麦克风
|
||
const postOpenMicrApi = async (enableMicr: boolean, uid: string, isAll?: boolean): Promise<void> => {
|
||
if (isAll) {
|
||
await PostMuteAll({
|
||
roomNum: state.channelId,
|
||
enableMicr
|
||
})
|
||
} else {
|
||
let msg = '';
|
||
if (uid === user.uid) {
|
||
await agora.getAudioMediaList().then(res => {
|
||
if (!res.ecordingList.length) {
|
||
msg = '未检测到麦克风!'
|
||
}
|
||
})
|
||
}
|
||
if (msg) {
|
||
message.error(msg)
|
||
return
|
||
}
|
||
await PostOpenMicr({
|
||
roomNum: state.channelId,
|
||
uid,
|
||
enableMicr
|
||
})
|
||
}
|
||
}
|
||
|
||
// 开关视频
|
||
const postOpenCamera = async (enableCamera: boolean, uid: string): Promise<void> => {
|
||
if (getUserRoomInfo()) {
|
||
let msg = '';
|
||
if (uid === user.uid) {
|
||
await agora.getVideoDeviceManager().then(res => {
|
||
if (!res.list.length) {
|
||
msg = '未检测到摄像头!'
|
||
}
|
||
})
|
||
}
|
||
if (msg) {
|
||
message.error(msg)
|
||
return
|
||
}
|
||
postOpenCameraApi(enableCamera, uid)
|
||
} else {
|
||
message.error(msgTips)
|
||
}
|
||
}
|
||
// 开关视频
|
||
const postOpenCameraApi = async (enableCamera: boolean, uid: string): Promise<void> => {
|
||
if (enableCamera) {
|
||
await agora.startCameraCapture()
|
||
} else {
|
||
await agora.stopCameraCapture();
|
||
}
|
||
await PostOpenCamera({
|
||
roomNum: state.channelId,
|
||
uid,
|
||
enableCamera
|
||
})
|
||
}
|
||
// 演讲者模式
|
||
// const changeSpeakerMode = (): void => {
|
||
// speakerModeModalRef.current.changeSpeakerMode()
|
||
// }
|
||
// 获取当前用户在房间的角色信息
|
||
const getUserRoomInfo = (list?: any): any => {
|
||
const data = list ? list : roomUserList
|
||
let userItem = data.find((item: any) => item.uid === userInfo.uid)
|
||
if (userItem && (userItem.roleId === '1' || userItem.isRoomManager)) {
|
||
return userItem
|
||
}
|
||
return ''
|
||
}
|
||
// 获取当前模式样式
|
||
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 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 = (item: any): void => {
|
||
if (isShare) {
|
||
allUserLook(String(isShare) === item.screenShareId ? item.screenShareId : item.uid)
|
||
} else {
|
||
allUserLook(item.uid)
|
||
}
|
||
}
|
||
return (
|
||
<>
|
||
<div className={styles.meeting} onClick={() => {
|
||
|
||
}}>
|
||
<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`}>
|
||
<div className={getMeetingContentBodyLeftModeClass()} >
|
||
{roomUserList.map((item: any, index: number) => {
|
||
return (index <= 19 ? <div
|
||
className={`${styles.meetingContentSwiperCard}`}
|
||
key={index}
|
||
onClick={() => {
|
||
if (String(isShare) === item.screenShareId) {
|
||
renderVideo(item.screenShareId)
|
||
} else {
|
||
renderVideo(item.uid)
|
||
}
|
||
}}
|
||
>
|
||
<div className={`${styles.meetingContentSwiperCardVdeio} ${item.roleId === '1' || item.isRoomManager ? styles.boxShadow : ''}`} id={`video-${item.uid}`}>
|
||
<div className={styles.meetingContentSwiperCardVdeioLoading}>
|
||
<Avatar name={item.userName} />
|
||
</div>
|
||
</div>
|
||
{meetingContentUser(item)}
|
||
{item.enableCamera ? null : meetingContentError(currentVideoId, item, item.roleId === '1' || item.isRoomManager)}
|
||
{String(isShare) === item.screenShareId ? <div className={styles.meetingContentSwiperCardShare}>
|
||
屏幕分享中
|
||
</div> : null}
|
||
{user.roleId === '1' ? <Popover placement="bottom" title={''} content={
|
||
<div className={styles.meetingContentSwiperCardPopover}>
|
||
{item.isRoomManager || user.roleId === '1' ? <Button
|
||
type="primary"
|
||
className='m-ant-btn'
|
||
size={'small'}
|
||
onClick={(event) => {
|
||
event.stopPropagation();
|
||
setAllUserLook(item)
|
||
}}
|
||
>全员看Ta</Button> : null}
|
||
{item.uid !== user.uid ? <Button
|
||
type="primary"
|
||
className='m-ant-btn'
|
||
size={'small'}
|
||
onClick={(event) => {
|
||
event.stopPropagation();
|
||
GetRoomKickout(state.channelId, item.uid)
|
||
}}
|
||
>踢出房间</Button> : null}
|
||
{item.uid !== user.uid ? <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}
|
||
</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()}`}>
|
||
<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()}`}>
|
||
<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(currentVideoId, currentLookUserAccount)}
|
||
</div> : null}
|
||
{currentLookUserStatus === 2 && currentLookUserAccount ?
|
||
<div className={`${styles.meetingContentSwiperCard} ${setMeetingContentSwiperCardClass()}`}>
|
||
<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()}`}>
|
||
<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(currentVideoId, currentLookUserAccount)}
|
||
</div> : null}
|
||
{currentLookUserStatus === 4 && currentLookUserAccount ?
|
||
<div className={`${styles.meetingContentSwiperCard} ${setMeetingContentSwiperCardClass()}`}>
|
||
<div className={`${styles.meetingContentSwiperCardVdeio}`} id='video-source-remote-screen'>
|
||
<div className={styles.meetingContentSwiperCardVdeioLoading}>
|
||
<Avatar name={currentLookUserAccount.userName} />
|
||
</div>
|
||
</div>
|
||
{meetingContentUser(currentLookUserAccount, true)}
|
||
</div> : null}
|
||
</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 ? <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>
|
||
<div>
|
||
<img src={item.enableMicr ? ImageUrl.icon22 : ImageUrl.icon22Active} alt="" onClick={() => {
|
||
postOpenMicr(!item.enableMicr, item.uid)
|
||
}} title={item.enableMicr ? '静音' : '解除声音'} />
|
||
</div>
|
||
<div>
|
||
<img src={item.enableCamera ? ImageUrl.icon23 : ImageUrl.icon23Active} alt="" onClick={() => {
|
||
postOpenCamera(!item.enableCamera, item.uid)
|
||
}} title={item.enableCamera ? '关闭视频' : '开启视频'} />
|
||
</div>
|
||
{item.uid !== user.uid && user.roleId === '1' ? <div>
|
||
<Popover placement="left" title={''} content={
|
||
<div className='drag'>
|
||
<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>
|
||
<Button
|
||
type="primary"
|
||
className='m-ant-btn'
|
||
style={{ width: '100%' }}
|
||
size={'small'}
|
||
onClick={() => {
|
||
GetRoomKickout(state.channelId, item.uid)
|
||
}}
|
||
>踢出房间</Button>
|
||
</div>
|
||
}>
|
||
<EllipsisOutlined style={{
|
||
color: '#fff',
|
||
fontSize: '20px'
|
||
}} />
|
||
</Popover>
|
||
</div> : null}
|
||
</div>
|
||
</div> : null
|
||
)
|
||
}
|
||
)}
|
||
</div>
|
||
<div className={`${styles.meetingUserListFooter} drag`}>
|
||
<div onClick={() => invitingPersonnelRef.current.changeInvitingPersonnelModal()}>邀请</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}>
|
||
{chatList.map((item: any, index: number) =>
|
||
<div
|
||
key={index}
|
||
className={`${item.uid !== state.uid ? styles.meetingUserChatContentLeft : styles.meetingUserChatContentRight} drag`}>
|
||
|
||
{user.roleId === '1' ? <Popover placement="bottom" title={''} content={
|
||
<div className={styles.meetingContentSwiperCardPopover}>
|
||
{item.isRoomManager || user.roleId === '1' ? <Button
|
||
type="primary"
|
||
className='m-ant-btn'
|
||
size={'small'}
|
||
onClick={(event) => {
|
||
event.stopPropagation();
|
||
setAllUserLook(item)
|
||
}}
|
||
>全员看Ta</Button> : null}
|
||
{item.uid !== user.uid ? <Button
|
||
type="primary"
|
||
className='m-ant-btn'
|
||
size={'small'}
|
||
onClick={(event) => {
|
||
event.stopPropagation();
|
||
GetRoomKickout(state.channelId, item.uid)
|
||
}}
|
||
>踢出房间</Button> : null}
|
||
{item.uid !== user.uid ? <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}
|
||
</div>
|
||
}>
|
||
<div>
|
||
<div><Avatar name={item.userName} /></div>
|
||
<span>{item.userName}</span>
|
||
</div>
|
||
</Popover> : <div>
|
||
<div><Avatar name={item.userName} /></div>
|
||
<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) => {
|
||
return (
|
||
row.title === '结束' ?
|
||
<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> :
|
||
<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 style={{ right: '-20px' }}>{roomUserList.length}</div> : null}
|
||
{row.title === '聊天' && noViewChatList > 0 ? <div style={{ right: '-30px' }}>{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={{ width: 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 = (currentVideoId: any, item: any, bool?: boolean) => {
|
||
return (
|
||
<>
|
||
<div
|
||
className={`${styles.meetingContentError} ${currentVideoId === item.uid ? styles.active : ''}`}
|
||
style={{ left: bool ? 'calc(50% + 2px)' : '', top: bool ? 'calc(50% + 2px)' : '' }}
|
||
>
|
||
<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
|