diff --git a/main.js b/main.js index 4b316bc..345ce0f 100644 --- a/main.js +++ b/main.js @@ -8,6 +8,7 @@ const { ipcMain, clipboard, dialog, + crashReporter, desktopCapturer, } = require('electron'); const path = require('node:path') @@ -16,6 +17,7 @@ const fs = require('fs'); const Registry = require('winreg'); const { autoUpdater, CancellationToken } = require('electron-updater'); const signalR = require('@microsoft/signalr'); +const { setTimeout, setInterval, clearTimeout, clearInterval } = require('timers'); const cancellationToken = new CancellationToken() app.allowRendererProcessReuse = false; let mainWindow = null; @@ -25,6 +27,7 @@ let env; let regKey; let connection = null; let envStr; +let startNumber = 0; class AppWindow extends BrowserWindow { constructor(config) { @@ -99,6 +102,11 @@ const additionalData = { myKey: 'myValue' } app.on('ready', () => { const gotTheLock = app.requestSingleInstanceLock(additionalData) if (gotTheLock) { + app.getPath('crashDumps') + crashReporter.start({ + uploadToServer: false, + ignoreSystemCrashHandler: false + }) env = process.argv.find((arg) => arg.startsWith('--env='))?.split('=')[1]; if (env === 'development') { Object.defineProperty(app, 'isPackaged', { @@ -141,11 +149,14 @@ app.on('ready', () => { }); ipcMain.handle('setEnv', (event, str) => { envStr = str; - updateHandle() // 检查更新 - setInterval(() => { - updateHandle() // 每一小时检查更新 - }, 1000 * 60 * 60) - createTray() + if (startNumber === 0) { + updateHandle() // 检查更新 + setInterval(() => { + updateHandle() // 每一小时检查更新 + }, 1000 * 60 * 60) + createTray() + startNumber++ + } }); // socket ipcMain.handle('startSignalr', (event, user) => { diff --git a/src/components/JoinSetting/index.tsx b/src/components/JoinSetting/index.tsx index af7fa60..3b902ec 100644 --- a/src/components/JoinSetting/index.tsx +++ b/src/components/JoinSetting/index.tsx @@ -9,6 +9,7 @@ import Avatar from '@/components/Avatar'; import { useNavigate } from 'react-router-dom'; import { agora } from '@/utils/package/agora'; import { role } from '@/config/role'; +const { setInterval, clearInterval } = require('timers'); let time = null as any; const JoinSetting = forwardRef((_props: any, ref: any) => { useImperativeHandle(ref, () => ({ diff --git a/src/components/UserVideo/index.module.scss b/src/components/UserVideo/index.module.scss index 7d7f7ac..b1d7b15 100644 --- a/src/components/UserVideo/index.module.scss +++ b/src/components/UserVideo/index.module.scss @@ -65,8 +65,8 @@ height: 0; .userVideoContentListItem { - height: 32%; - width: calc(100% / 4 - 8px); + height: 13%; + width: calc(100% / 3 - 8px); padding: 4px; .userVideoContentListItemVideo { diff --git a/src/components/UserVideo/index.tsx b/src/components/UserVideo/index.tsx index de403fe..c87e527 100644 --- a/src/components/UserVideo/index.tsx +++ b/src/components/UserVideo/index.tsx @@ -6,6 +6,7 @@ import { Button, Empty, Select, message } from 'antd'; import { useEffect, useState } from "react"; import { useLocation } from 'react-router'; import { VideoStreamType } from 'agora-electron-sdk'; +const { setInterval, clearInterval } = require('timers'); const UserVideo: React.FC = () => { const { state } = useLocation(); const [from, setFrom] = useState({ diff --git a/src/page/Home/Index/index.tsx b/src/page/Home/Index/index.tsx index 98a6734..e2a3462 100644 --- a/src/page/Home/Index/index.tsx +++ b/src/page/Home/Index/index.tsx @@ -13,6 +13,7 @@ import { role } from '@/config/role'; import dayjs from 'dayjs'; import StupWizard from '@/components/StupWizard'; import { GetSubDpList } from '@/api/Home/User'; +const { setInterval, clearInterval } = require('timers'); const fs = require('fs').promises; const { exec } = require('child_process'); const { RangePicker } = DatePicker; diff --git a/src/page/Home/index.tsx b/src/page/Home/index.tsx index 1e202d1..04f99fb 100644 --- a/src/page/Home/index.tsx +++ b/src/page/Home/index.tsx @@ -8,6 +8,7 @@ import { storage } from '@/utils'; import ImageUrl from '@/utils/package/imageUrl' import Avatar from '@/components/Avatar'; import StupWizard from '@/components/StupWizard'; +const { setInterval, clearInterval } = require('timers'); dayjs.locale('zh-cn'); type navListType = { title: string; diff --git a/src/page/Meeting/ShareScreenWindow/index.tsx b/src/page/Meeting/ShareScreenWindow/index.tsx index 5a29c8a..772f6b2 100644 --- a/src/page/Meeting/ShareScreenWindow/index.tsx +++ b/src/page/Meeting/ShareScreenWindow/index.tsx @@ -7,6 +7,7 @@ import { CaretDownOutlined, CaretUpOutlined } from '@ant-design/icons'; import { Button } from 'antd'; import dayjs from 'dayjs'; import { useEffect, useState } from "react"; +const { setInterval, clearInterval } = require('timers'); const ShareScreenWindow: React.FC = () => { const [footerLists, setFooterLists] = useState([ { @@ -54,6 +55,11 @@ const ShareScreenWindow: React.FC = () => { let timeout: NodeJS.Timeout; useEffect(() => { getRoomUser() + if (!role.ID.includes(userInfo.roleId)) { + setFooterLists((res: any) => { + return res.splice(4, 1) + }) + } channel.onmessage = function (event) { let { type, time } = event.data; switch (type) { @@ -82,8 +88,10 @@ const ShareScreenWindow: React.FC = () => { footerListTemplate[0].active = data.parmes.footerList[0][0].active; footerListTemplate[1].title = data.parmes.footerList[0][1].active ? '开启视频' : '关闭视频'; footerListTemplate[1].active = data.parmes.footerList[0][1].active; - footerListTemplate[4].title = data.parmes.footerList[1][3].active ? '录制中' : '录制'; - footerListTemplate[4].active = data.parmes.footerList[1][3].active; + if (role.ID.includes(userInfo.roleId)) { + footerListTemplate[4].title = data.parmes.footerList[1][3].active ? '录制中' : '录制'; + footerListTemplate[4].active = data.parmes.footerList[1][3].active; + } setFooterLists(footerListTemplate) break; case 'roomUserList': diff --git a/src/page/Meeting/index.tsx b/src/page/Meeting/index.tsx index bf368ba..07c2de6 100644 --- a/src/page/Meeting/index.tsx +++ b/src/page/Meeting/index.tsx @@ -24,6 +24,7 @@ import { fixWebmDuration } from "webm-duration-fix-buffer"; import { getKeyOpenChildWindow, setKeyOpenChildWindow } from '@/utils/package/public'; import MeetingDisconnected from '@/components/MeetingDisconnected'; import SingIn from '@/components/SingIn'; +const { setTimeout, setInterval, clearTimeout, clearInterval } = require('timers'); const { confirm } = Modal; const { exec } = require('child_process'); const fs = require('fs').promises; @@ -435,27 +436,31 @@ const Meeting: React.FC = () => { if (!data) { setIsScreenCapture(bool => { if (!bool) { - confirm({ - title: '提示', - icon: , - content: `是否录制本次会议?`, - centered: true, - okText: '确定', - cancelText: '取消', - async onOk() { - if (stateInfo) { - changeStatusList({ - title: '录制' - }, 1, 3) - } else { - message.error('当前不在会议室!') + if (role.ID.includes(userInfo.roleId)) { + confirm({ + title: '提示', + icon: , + content: `是否录制本次会议?`, + centered: true, + okText: '确定', + cancelText: '取消', + async onOk() { + if (stateInfo) { + changeStatusList({ + title: '录制' + }, 1, 3) + } else { + message.error('当前不在会议室!') + } + showSingIn() + }, + onCancel() { + showSingIn() } - showSingIn() - }, - onCancel() { - showSingIn() - } - }) + }) + } else { + showSingIn() + } } return bool }) @@ -914,12 +919,18 @@ const Meeting: React.FC = () => { observer?.unobserve(element); }); const observerObject = new IntersectionObserver(async (entries: IntersectionObserverEntry[], _observer: IntersectionObserver) => { - entries.forEach(async (entry) => { - if (entry.target.id !== user.uid) { - await agora.muteRemoteVideoStreamEx(Number(entry.target.id), !entry.isIntersecting) - } - }); - await agora.muteRemoteVideoStreamEx(Number(currentVideoId), false) + setIsScreenCapture((bool: boolean) => { + entries.forEach(async (entry) => { + if (entry.target.id !== user.uid) { + await agora.muteRemoteVideoStreamEx(Number(entry.target.id), bool ? true : !entry.isIntersecting) + } + }); + return bool + }) + setIsScreenCapture((bool: boolean) => { + agora.muteRemoteVideoStreamEx(Number(currentVideoId), bool) + return bool + }) }, { threshold: 0, root: document.getElementById('videoView') }); setObserver(observerObject) elements.forEach(element => { @@ -1611,11 +1622,11 @@ const Meeting: React.FC = () => { } // 退出房间 const leaveChannel = async (bool: boolean = true): Promise => { + await stopScreenCapture() await stopRecorderMedia() if (bool) { await getLeave() } - await stopScreenCapture() await agora.leaveChannel() setTimeout(() => { if (userInfo.isAnonymous) { @@ -1623,11 +1634,20 @@ const Meeting: React.FC = () => { } else { navigate('/home/index') } - }, 0) + }, 1000) } // 分享屏幕 const clickSharedScreen = async (): Promise => { let data = sharedScreenList.find((item: any) => item.sourceId === sharedScreenItem.sourceId) + const elements = document.querySelectorAll('.intersectionObserver-view'); + if (elements.length) { + elements.forEach(item => { + if (item.id !== userInfo.uid) { + agora.muteRemoteVideoStreamEx(Number(item.id), true) + } + }); + agora.setSubscribeVideoBlocklist([Number(user.screenShareId)], 1) + } if (data) { const footerListTemplate = [...footerList] footerListTemplate[footerListIndex.itemIndex][footerListIndex.rowIndex].title = '停止共享' @@ -2667,7 +2687,7 @@ const Meeting: React.FC = () => { : -
+
会议监控 { @@ -2837,6 +2857,21 @@ const Meeting: React.FC = () => { {row.title}
+ case '录制': + case '录制中': + if (role.ID.includes(user.roleId)) { + return
changeStatusList(row, itemIndex, rowIndex)} + onMouseDown={() => changeFooterListSelect(row, itemIndex, rowIndex, true)} + onMouseUp={() => changeFooterListSelect(row, itemIndex, rowIndex, false)} + onMouseLeave={() => changeFooterListSelect(row, itemIndex, rowIndex, false)} + key={rowIndex}> + {row.select ? : } + {row.title} +
+ } + return null default: return
{ if (item.view?.childNodes.length === 1 || item.type) { + if (String(item.uid).length === 9) { + return + } await rtcEngine.setupLocalVideo({ renderMode: agora.getRrenderMode(item.uid), sourceType: item.sourceType, @@ -302,6 +308,7 @@ export const agora = { publishMicrophoneTrack: true,//设置是否发布麦克风采集到的音频 publishCameraTrack: true,//设置是否发布摄像头采集的视频 publishScreenTrack: false,//设置是否发布屏幕采集的视频 + audienceLatencyLevel: bool ? AudienceLatencyLevelType.AudienceLatencyLevelUltraLowLatency : AudienceLatencyLevelType.AudienceLatencyLevelLowLatency, }) }, // 设置接收大小流 @@ -342,6 +349,7 @@ export const agora = { publishMicrophoneTrack: false,//设置是否发布麦克风采集到的音频 publishCameraTrack: true,//设置是否发布摄像头采集的视频 publishScreenTrack: false,//设置是否发布屏幕采集的视频 + audienceLatencyLevel: bool ? AudienceLatencyLevelType.AudienceLatencyLevelLowLatency : AudienceLatencyLevelType.AudienceLatencyLevelUltraLowLatency, } ); await rtcEngine.setDualStreamModeEx( @@ -365,6 +373,10 @@ export const agora = { muteRemoteVideoStreamEx: async (uid: number, mute: boolean) => { await rtcEngine.muteRemoteVideoStreamEx(uid, mute, { channelId: option.channelId, localUid: Number(option.uid) }) }, + // 设置视频订阅黑名单。 + setSubscribeVideoBlocklist: async (uidList: number[], uidNumber: number) => { + await rtcEngine.setSubscribeVideoBlocklist(uidList, uidNumber) + }, // 取消或恢复订阅指定远端用户的音频流 muteRemoteVideoStream: async (uid: number, mute: boolean) => { rtcEngine.muteRemoteVideoStream(uid, mute) @@ -396,6 +408,7 @@ export const agora = { publishMicrophoneTrack: publishMicrophoneTrack,//设置是否发布麦克风采集到的音频 publishCameraTrack: publishCameraTrack,//设置是否发布摄像头采集的视频 publishScreenTrack: false,//设置是否发布屏幕采集的视频 + audienceLatencyLevel: data ? AudienceLatencyLevelType.AudienceLatencyLevelUltraLowLatency : AudienceLatencyLevelType.AudienceLatencyLevelLowLatency, }) }, // 取消或恢复发布本地视频流 @@ -408,6 +421,7 @@ export const agora = { publishMicrophoneTrack: publishMicrophoneTrack,//设置是否发布麦克风采集到的音频 publishCameraTrack: publishCameraTrack,//设置是否发布摄像头采集的视频 publishScreenTrack: false,//设置是否发布屏幕采集的视频 + audienceLatencyLevel: data ? AudienceLatencyLevelType.AudienceLatencyLevelUltraLowLatency : AudienceLatencyLevelType.AudienceLatencyLevelLowLatency, }) }, // 摄像头采集 diff --git a/vite.config.ts b/vite.config.ts index f19a94c..e3110f4 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -76,7 +76,8 @@ export default defineConfig({ BeautyOptions, ColorEnhanceOptions, LowlightEnhanceOptions, - VirtualBackgroundSource + VirtualBackgroundSource, + AudienceLatencyLevelType } = require("agora-electron-sdk") export { createAgoraRtcEngine, @@ -101,7 +102,8 @@ export default defineConfig({ BeautyOptions, ColorEnhanceOptions, LowlightEnhanceOptions, - VirtualBackgroundSource + VirtualBackgroundSource, + AudienceLatencyLevelType } `, })