import styles from '@/page/Meeting/index.module.scss' import { useEffect, useRef, useState } from "react"; import Operation from '@/components/Operation'; import { Button, Input, Popover, Modal, Checkbox, message, Table, Pagination } from "antd"; import { DeleteOutlined, ProfileOutlined, ReloadOutlined, SearchOutlined, VerticalAlignBottomOutlined } from '@ant-design/icons'; import { useLocation, useNavigate } from 'react-router-dom'; import { thumbImageBufferToBase64 } from '@/utils/package/base64' import { storage } from '@/utils'; import { GetRoomFile, PostRoomFile, DeleteRoomFile, GetRoomUpFileurl, GetRoomFileDwUrl, GetRoomUser, PostOpenMicr, PostOpenCamera, PostRoomManager, DeleteRoomManager, GetRoomKickout } from '@/api/Meeting'; import axios from 'axios'; import ImageUrl from '@/utils/package/imageUrl' import agora from '@/utils/package/agora' import SpeakerModeModal from '@/components/SpeakerModeModal'; import { onInvoke, onSignalr, offSignalr } from '@/utils/package/signalr'; import dayjs from 'dayjs'; import durationPlugin from 'dayjs/plugin/duration'; import { VideoSourceType } from 'agora-electron-sdk'; dayjs.extend(durationPlugin); const { Column } = Table const Meeting: React.FC = () => { const navigate = useNavigate(); const { state } = useLocation(); const speakerModeModalRef = useRef(); const [statusList, setStatusList] = useState({ userList: false, userChatList: false, }) const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [isSharedScreenModal, setIsSharedScreenModal] = useState(false); const [isInit, setIsInit] = useState(true); const [user, setUser] = useState({}); const [showRowSelection, setShowRowSelection] = useState(false); const [isSharedFilesModel, setIsSharedFilesModel] = useState(false); const [sharedScreenList, setSharedScreenList] = useState([]); const [sharedScreenItem, setSharedScreenItem] = useState(''); 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({ itemIndex: 0, rowIndex: 0, }); const [fileList, setFileList] = useState({ data: [], keyword: '', total: 0, pageIndex: 1, pageSize: 10, }) const [roomUserList, setRoomUserList] = useState([]) const [chatList, setChatList] = useState([]) const [currentVideoId, setCurrentVideoId] = useState('') let [currentSeconds, setCurrentSeconds] = useState(0) const [currentEffective, setCurrentEffective] = useState(0) const [open, setOpen] = useState(false) const [meetingMode, setMeetingMode] = useState('') useEffect(() => { let time = null as any; if (isInit) { let userInfo = JSON.parse(storage.getItem('user') as string) setMeetingMode(storage.getItem('meetingMode') as string || 'FreedomMode'); agora.init() agora.setCameraCapture(VideoSourceType.VideoSourceCameraPrimary) agora.setJoinChannel({ channelId: state.channelId, userid: userInfo.account, token: state.token, }) setCurrentVideoId(userInfo.account) setUser(userInfo) setIsInit(false) window.addEventListener('customStorageChange', handleCustomStorageChange); time = setInterval(() => { let effectiveTypeLength = ['slow-2g', '2g', '3g', '4g'].indexOf((navigator as any).connection.effectiveType) if (effectiveTypeLength >= 0) { setCurrentEffective(effectiveTypeLength + 1) } setCurrentSeconds(currentSeconds++) }, 1000) } else { getRoomFile() } return () => { window.removeEventListener('customStorageChange', handleCustomStorageChange); clearInterval(time) }; }, [fileList.pageIndex]); useEffect(() => { roomUserList.forEach((item: any) => { if (item.account === user.account) { const footerListTemplate = [...footerList] footerListTemplate[0][0].title = item.enableMicr ? '关闭声音' : '开启声音' footerListTemplate[0][0].active = !item.enableMicr setFooterList(footerListTemplate) } agora.muteLocalAudioStream(!item.enableMicr) agora.muteLocalVideoStream(!item.enableCamera) let dom = document.getElementById(`video-${item.account}`) as HTMLElement if (!dom.getAttribute('account')) { dom.setAttribute('account', item.account) agora.setVideo({ account: Number(item.account), view: dom, channelId: state.channelId, }) } }); }, [roomUserList]); useEffect(() => { onSignalr((item: any) => { switch (item.key) { case 'ReceiveMessage': setChatList((newChatList: any) => [...newChatList, item]) break; case 'RefreshUserList': getRoomUser() break; case 'Operation': // 1:全员退出会议 // 2:设置取消管理员 // 3:踢出房间 switch (item.type) { case 1: leaveChannel() break; case 2: case 3: getRoomUser() break; } break; case 'ForceExitRoom': leaveChannel() break; case 'RefreshView': setMeetingMode(item.type) break; } }) return () => { offSignalr() } }, []) useEffect(() => { }, [currentVideoId]) // 加入房间时间 const changeCurrentSeconds = () => { 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 => { const footerListTemplate = [...footerList] setFooterListIndex({ itemIndex, rowIndex, }) switch (row.title) { case '成员列表': setStatusList({ userList: true, userChatList: false, }) break; case '聊天': setStatusList({ userList: false, userChatList: true, }) break; case '共享屏幕': getDesktopCapturerVideo() setIsSharedScreenModal(true) break; case '停止共享': agora.setCameraCapture(VideoSourceType.VideoSourceCameraPrimary) footerListTemplate[itemIndex][rowIndex].title = '共享屏幕' break; case '关闭声音': footerListTemplate[itemIndex][rowIndex].title = '开启声音' footerListTemplate[itemIndex][rowIndex].active = true setFooterList(footerListTemplate) postOpenMicr(false) break; case '开启声音': footerListTemplate[itemIndex][rowIndex].title = '关闭声音' footerListTemplate[itemIndex][rowIndex].active = false setFooterList(footerListTemplate) postOpenMicr(true) break; case '关闭视频': footerListTemplate[itemIndex][rowIndex].title = '开启视频' footerListTemplate[itemIndex][rowIndex].active = true setFooterList(footerListTemplate) postOpenCamera(false) break; case '开启视频': footerListTemplate[itemIndex][rowIndex].title = '关闭视频' footerListTemplate[itemIndex][rowIndex].active = false setFooterList(footerListTemplate) postOpenCamera(true) break; case '设置向导': break; case '录制': if (currentVideoId === user.account) { message.error('请勿自己录制自己!') } else { footerListTemplate[itemIndex][rowIndex].title = '录制中' footerListTemplate[itemIndex][rowIndex].active = true setFooterList(footerListTemplate) agora.startRecording(Number(currentVideoId)) } break; case '录制中': footerListTemplate[itemIndex][rowIndex].title = '录制' footerListTemplate[itemIndex][rowIndex].active = false setFooterList(footerListTemplate) agora.stopRecording() break; case '共享文件': await getRoomFile() setIsSharedFilesModel(true) break; } } // 退出房间 const leaveChannel = (): void => { agora.leaveChannel() agora.stopScreenCapture() navigate(-1) } // 分享屏幕 const clickSharedScreen = async (): Promise => { let data = sharedScreenList.find((item: any) => item.sourceId === sharedScreenItem.sourceId) if (data) { const footerListTemplate = [...footerList] footerListTemplate[footerListIndex.itemIndex][footerListIndex.rowIndex].title = '停止共享' setIsSharedScreenModal(false) agora.setDesktopCapturerVideo(sharedScreenItem, VideoSourceType.VideoSourceScreen) } 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 getRoomFile = async (): Promise => { await GetRoomFile({ pageIndex: fileList.pageIndex, pageSize: fileList.pageSize, keyword: fileList.keyword, roomId: state.roomId }).then(res => { if (res.code === 200) { setFileList({ ...fileList, data: res.data.items.map((item: any) => { return { ...item, key: item.id, } }), total: res.data.total }) } }) } // 获取房间用户 const getRoomUser = async (): Promise => { await GetRoomUser(state.channelId).then(res => { if (res.code === 200) { setRoomUserList(res.data) } }) } const handleCustomStorageChange = async (e: any): Promise => { switch (e.key) { case 'isJoin': if (e.value) { await onInvoke('joinChannel', { roomNum: state.channelId, enableMicr: true, enableCamera: true }) getRoomUser() } else { onInvoke('levelChannel', { roomNum: state.channelId }) } break; case 'isRemotJoin': setTimeout(() => { getRoomUser() }, 2000) break; case 'meetingMode': setMeetingMode(e.value) break; } }; // 聊天发送 const sendMsg = (): void => { if (textMsg) { onInvoke('sendChannelMsg', { roomNum: state.channelId, msg: textMsg, }) setChatList((newChatList: any) => [...newChatList, { uid: state.uid, userName: user.userName, message: textMsg, }]) setTextMsg('') } else { message.success('请输入内容!') } } // 开关麦克风 const postOpenMicr = async (enableMicr: boolean, isAll?: boolean): Promise => { await PostOpenMicr({ roomNum: state.channelId, uid: user.uid, enableMicr, isAll, }) } // 开关视频 const postOpenCamera = async (enableCamera: boolean): Promise => { await PostOpenCamera({ roomNum: state.channelId, uid: user.uid, enableCamera }) } // 演讲者模式 const changeSpeakerMode = (): void => { speakerModeModalRef.current.changeSpeakerMode() } // 获取当前模式样式 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 = (account: string): string => { if (currentVideoId === account && (meetingMode === 'StandardMode' || meetingMode === 'SpeakerMode')) { switch (meetingMode) { case 'StandardMode': return styles.meetingContentSwiperCardStandardMode case 'SpeakerMode': return styles.meetingContentSwiperCardSpeakerMode } } return '' } return ( <>
{currentEffective >= 1 ? : null} {currentEffective >= 2 ? : null} {currentEffective >= 3 ? : null} {currentEffective >= 4 ? : null}
{changeCurrentSeconds()}
会议号:{state.channelId}
{getMeetingContentBodyLeftModeText()}
{roomUserList.map((item: any, index: number) =>
{ setCurrentVideoId(item.account) }} >
暂无视频
{meetingContentUser(item)} {item.enableCamera ? null : meetingContentError()}
)}
{ (statusList.userList || statusList.userChatList) ? (
{statusList.userList ?
成员列表 { setStatusList({ userList: false, userChatList: false, }) }} />
} />
{roomUserList.map((item: any, index: number) =>
{item.userName} {item.roleId === '1' || item.isManager ? {item.roleId === '1' ? '主持人' : '临时主持人'} : null}
{item.account !== user.account && user.roleId === '1' ?
{!item.isManager ? : }
: null}
)}
邀请
postOpenMicr(false, true)}>全员静音
:
聊天 { setStatusList({ userList: false, userChatList: false, }) }} />
{chatList.map((item: any, index: number) =>
{item.userName}
{item.message}
)}
{ setTextMsg(e.target.value) }} onPressEnter={sendMsg}>
}
) : null }
{footerList.map((item, itemIndex) => { return (
{item.map((row, rowIndex) => { return ( row.title === '结束' ?
{ await onInvoke('sendOper', { roomNum: state.channelId, type: 1, }) leaveChannel() }}>全员结束会议
leaveChannel()}>仅自己离开
{ setOpen(false) }}>取消
} title="" trigger="click" open={open} onOpenChange={() => setOpen(true)} >
{row.title}
:
changeStatusList(row, itemIndex, rowIndex)} key={rowIndex}> {row.title}
) })}
) })}
{sharedScreenList.map((item: any, index: number) => { return (
{ setSharedScreenItem(item) }}> {item.iconDataUrl ? : ''}
{item.sourceTitle}
) })}
{ }}>共享电脑音频
setIsSharedFilesModel(false)} maskClosable >
共{fileList.total}个文件
} onChange={(e) => { setFileList({ ...fileList, keyword: e.target.value }) }} onBlur={() => { if (fileList.pageIndex === 1) { getRoomFile() } else { setFileList({ ...fileList, pageIndex: 1 }) } }} /> { if (fileList.pageIndex === 1) { getRoomFile() } else { setFileList({ ...fileList, pageIndex: 1 }) } }} /> { setShowRowSelection(!showRowSelection) }} style={{ color: showRowSelection ? '#5575F2' : 'white' }} /> {showRowSelection ? { if (selectedRowKeys.length) { DeleteRoomFile(selectedRowKeys).then(res => { if (res.code === 200) { message.success('删除成功!') getRoomFile() } }) } else { message.error('请选择文件!') } }} /> : null}
{ setSelectedRowKeys(newSelectedRowKeys); } } : undefined} dataSource={fileList.data} pagination={false} scroll={{ y: '40vh' }} style={{ width: '100%' }} > ( <> {item.size / 1024 > 1000 ? (item.size / (1024 * 1024)).toFixed(2) + 'MB' : (item.size / 1024).toFixed(2) + 'KB'} )} /> ( <> {item.downloadCount}次 )} /> ( <> { GetRoomFileDwUrl(item.fileUrl, item.id).then(res => { if (res.code === 200) { const downloadLink = document.createElement("a"); downloadLink.href = res.data; downloadLink.download = item.fileName; downloadLink.click(); getRoomFile() } }) }} /> {/* */} )} />
{ setFileList({ ...fileList, pageIndex: e }) }} pageSize={fileList.pageSize} current={fileList.pageIndex} hideOnSinglePage={true} />
) } const meetingContentUser = (item: any) => { return ( <>
{item.roleId === '1' || item.isManager ?
: null}
{item.userName}
) } const meetingContentError = () => { return ( <>
无视频
) } export default Meeting