diff --git a/main.js b/main.js index 03ad5de..f8b4a61 100644 --- a/main.js +++ b/main.js @@ -16,6 +16,7 @@ const { autoUpdater, CancellationToken } = require('electron-updater'); const cancellationToken = new CancellationToken() app.allowRendererProcessReuse = false; let mainWindow = null; +let newWindow = null; let isMaximized = false; let env; @@ -264,32 +265,43 @@ app.on('ready', () => { }); // 打开新窗口 ipcMain.handle('oepnWindow', (event, data) => { - const newWindow = new BrowserWindow({ - width: 1200, - height: 800, - minWidth: 1200, - minHeight: 800, - webPreferences: { - contextIsolation: false, - nodeIntegration: true, - enableRemoteModule: true, - nodeIntegrationInWorker: true, - allowMediaDevices: true, - preload: path.join(__dirname, 'preload.js') - }, - frame: false, - backgroundColor: '#00000000', - transparent: true, - }); - newWindow.loadURL(data.url); - newWindow.focus(); - newWindow.webContents.on('before-input-event', (event, input) => { - if (env === 'development') { - if (input.key === 'F12') { - newWindow.webContents.openDevTools() + if (newWindow) { + newWindow.focus(); + } else { + newWindow = new BrowserWindow({ + width: 1200, + height: 800, + minWidth: 1200, + minHeight: 800, + webPreferences: { + contextIsolation: false, + nodeIntegration: true, + enableRemoteModule: true, + nodeIntegrationInWorker: true, + allowMediaDevices: true, + preload: path.join(__dirname, 'preload.js') + }, + frame: false, + backgroundColor: '#00000000', + transparent: true, + }); + newWindow.loadURL(data.url); + newWindow.focus(); + newWindow.webContents.on('before-input-event', (event, input) => { + if (env === 'development') { + if (input.key === 'F12') { + newWindow.webContents.openDevTools() + } } - } - }); + }); + } + }); + // 关闭会议监控窗口 + ipcMain.handle('closeMonitorWindow', () => { + if (newWindow) { + newWindow.close(); + newWindow = null + } }); } }); diff --git a/package-lock.json b/package-lock.json index 6ce43e2..ba578fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,16 @@ { - "name": "multi.person.meeting", + "name": "WGShare.Metting", "version": "0.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "multi.person.meeting", + "name": "WGShare.Metting", "version": "0.0.1", "dependencies": { "@ant-design/icons": "^5.3.7", "@microsoft/signalr": "^8.0.0", + "@reduxjs/toolkit": "^2.2.7", "@types/node": "^20.14.9", "agora-electron-sdk": "^4.3.2", "animate.css": "^4.1.1", @@ -24,6 +25,7 @@ "postcss-px-to-viewport-8-plugin": "^1.2.5", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-redux": "^9.1.2", "react-router-dom": "^6.23.1", "sass": "^1.77.5", "tldraw": "^2.2.0" @@ -2243,6 +2245,29 @@ "react-dom": ">=16.9.0" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.2.7", + "resolved": "https://registry.npmmirror.com/@reduxjs/toolkit/-/toolkit-2.2.7.tgz", + "integrity": "sha512-faI3cZbSdFb8yv9dhDTmGwclW0vk0z5o1cia+kf7gCbaCwHI5e+7tP57mJUv22pNcNbeA62GSrPpfrUfdXcQ6g==", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@remix-run/router": { "version": "1.17.1", "resolved": "https://registry.npmmirror.com/@remix-run/router/-/router-1.17.1.tgz", @@ -2519,6 +2544,11 @@ "resolved": "https://registry.npmmirror.com/@types/triple-beam/-/triple-beam-1.3.5.tgz", "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@types/verror": { "version": "1.10.10", "resolved": "https://registry.npmmirror.com/@types/verror/-/verror-1.10.10.tgz", @@ -7265,6 +7295,15 @@ } ] }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/immutable": { "version": "4.3.6", "resolved": "https://registry.npmmirror.com/immutable/-/immutable-4.3.6.tgz", @@ -9620,6 +9659,28 @@ "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, + "node_modules/react-redux": { + "version": "9.1.2", + "resolved": "https://registry.npmmirror.com/react-redux/-/react-redux-9.1.2.tgz", + "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.13.0", "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.13.0.tgz", @@ -9863,6 +9924,19 @@ "node": ">=0.10.0" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", @@ -9999,6 +10073,11 @@ "resolved": "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" + }, "node_modules/resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", @@ -11675,6 +11754,14 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/utf8-byte-length": { "version": "1.0.5", "resolved": "https://registry.npmmirror.com/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", @@ -13473,6 +13560,17 @@ "rc-util": "^5.38.0" } }, + "@reduxjs/toolkit": { + "version": "2.2.7", + "resolved": "https://registry.npmmirror.com/@reduxjs/toolkit/-/toolkit-2.2.7.tgz", + "integrity": "sha512-faI3cZbSdFb8yv9dhDTmGwclW0vk0z5o1cia+kf7gCbaCwHI5e+7tP57mJUv22pNcNbeA62GSrPpfrUfdXcQ6g==", + "requires": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + } + }, "@remix-run/router": { "version": "1.17.1", "resolved": "https://registry.npmmirror.com/@remix-run/router/-/router-1.17.1.tgz", @@ -13721,6 +13819,11 @@ "resolved": "https://registry.npmmirror.com/@types/triple-beam/-/triple-beam-1.3.5.tgz", "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" }, + "@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "@types/verror": { "version": "1.10.10", "resolved": "https://registry.npmmirror.com/@types/verror/-/verror-1.10.10.tgz", @@ -17293,6 +17396,11 @@ "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, + "immer": { + "version": "10.1.1", + "resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==" + }, "immutable": { "version": "4.3.6", "resolved": "https://registry.npmmirror.com/immutable/-/immutable-4.3.6.tgz", @@ -19025,6 +19133,15 @@ "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, + "react-redux": { + "version": "9.1.2", + "resolved": "https://registry.npmmirror.com/react-redux/-/react-redux-9.1.2.tgz", + "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", + "requires": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + } + }, "react-refresh": { "version": "0.13.0", "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.13.0.tgz", @@ -19193,6 +19310,17 @@ } } }, + "redux": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "requires": {} + }, "regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", @@ -19298,6 +19426,11 @@ "resolved": "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, + "reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" + }, "resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", @@ -20592,6 +20725,12 @@ "tslib": "^2.0.0" } }, + "use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "requires": {} + }, "utf8-byte-length": { "version": "1.0.5", "resolved": "https://registry.npmmirror.com/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", diff --git a/package.json b/package.json index d041df3..a2850fb 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "dependencies": { "@ant-design/icons": "^5.3.7", "@microsoft/signalr": "^8.0.0", + "@reduxjs/toolkit": "^2.2.7", "@types/node": "^20.14.9", "agora-electron-sdk": "^4.3.2", "animate.css": "^4.1.1", @@ -39,6 +40,7 @@ "postcss-px-to-viewport-8-plugin": "^1.2.5", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-redux": "^9.1.2", "react-router-dom": "^6.23.1", "sass": "^1.77.5", "tldraw": "^2.2.0" @@ -100,4 +102,4 @@ "shortcutName": "智汇享" } } -} \ No newline at end of file +} diff --git a/preload.js b/preload.js index ed49ae0..e19952f 100644 --- a/preload.js +++ b/preload.js @@ -65,4 +65,8 @@ window.electron = { oepnWindow: (data) => { ipcRenderer.invoke('oepnWindow', data) }, + // 关闭会议监控窗口 + closeMonitorWindow: () => { + ipcRenderer.invoke('closeMonitorWindow') + }, } diff --git a/src/App.tsx b/src/App.tsx index dcb68de..67338fb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -33,8 +33,7 @@ const App: React.FC = () => { }); const [spinning, setSpinning] = useState(false); const [isState, setIsState] = useState(true); - const urlWindow = ['#/userVideo'] - if (urlWindow.indexOf(location.hash) === -1) { + if (location.href.indexOf('#/userVideo') === -1) { useEffect(() => { let userInfo = JSON.parse(storage.getItem('user') as string) let loginInfo = JSON.parse(storage.getItem('login') as string) @@ -151,6 +150,9 @@ const App: React.FC = () => { if (location.href.indexOf('/login') !== -1) { onStop() } + if (location.href.indexOf('#/meeting') === -1) { + window.electron.closeMonitorWindow() + } }, [navigate]) } useEffect(() => { diff --git a/src/api/Meeting/index.ts b/src/api/Meeting/index.ts index db5ed89..1c2ba98 100644 --- a/src/api/Meeting/index.ts +++ b/src/api/Meeting/index.ts @@ -129,4 +129,9 @@ export const GetApplySpeak = (roomNum: string) => request({ url: `/room/apply-speak?roomNum=${roomNum}`, method: 'get' + }) +export const GetPolling = (roomNum: string, count: string) => + request({ + url: `/room/polling?roomNum=${roomNum}&count=${count}`, + method: 'get' }) \ No newline at end of file diff --git a/src/components/JoinSetting/index.tsx b/src/components/JoinSetting/index.tsx index e31ee68..3fabbae 100644 --- a/src/components/JoinSetting/index.tsx +++ b/src/components/JoinSetting/index.tsx @@ -71,9 +71,12 @@ const JoinSetting = forwardRef((_props: any, ref: any) => { }) } const getRoomRtcToken = async (roomNum: string, callBack: Function): Promise => { - await GetRoomRtcToken(roomNum).then(res => { - if (res.code === 200) { - callBack(res.data) + Promise.all([GetRoomRtcToken(roomNum), GetRoomRtcToken(roomNum + 'a')]).then(res => { + if (res[0].code === 200 && res[1].code === 200) { + callBack({ + token: res[0].data, + tokenA: res[1].data, + }) } }) } @@ -123,7 +126,7 @@ const JoinSetting = forwardRef((_props: any, ref: any) => { { joinRoomSettingForm.map((item, index) => { return
{ - if (user.roleId === '1'){ + if (user.roleId === '1') { let msg = ''; if (index === 0) { await agora.getAudioMediaList().then(res => { @@ -150,7 +153,7 @@ const JoinSetting = forwardRef((_props: any, ref: any) => { agora.startPreview('videoPreview', Number(user.account)) } } - }else{ + } else { message.error('您不是管理员,无法开启此功能!') } }}> @@ -169,8 +172,8 @@ const JoinSetting = forwardRef((_props: any, ref: any) => { } isGetCheckoutRoomNum(roomNumber, (bool: boolean) => { if (bool) { - getRoomRtcToken(roomNumber, (token: string) => { - if (token) { + getRoomRtcToken(roomNumber, (options: any) => { + if (options) { postRefresh(() => { clearInterval(time) setJoinRoomSettingModal(false) @@ -180,7 +183,8 @@ const JoinSetting = forwardRef((_props: any, ref: any) => { navigate(`/meeting`, { state: { channelId: roomNumber, - token, + token: options.token, + tokenA: options.tokenA, roomId: res.data.id, roomName: res.data.roomName, enableMicr: joinRoomSettingForm[0].active, diff --git a/src/main.tsx b/src/main.tsx index 2931b2a..1ec4089 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -5,10 +5,14 @@ import { HashRouter } from 'react-router-dom'; import { ConfigProvider } from 'antd'; import zhCN from 'antd/locale/zh_CN'; import 'animate.css'; +import { Provider } from 'react-redux' +import store from '@/utils/package/store'; ReactDOM.createRoot(document.getElementById('root')!).render( - + + + ) diff --git a/src/page/Meeting/index.tsx b/src/page/Meeting/index.tsx index be66bc3..99b0274 100644 --- a/src/page/Meeting/index.tsx +++ b/src/page/Meeting/index.tsx @@ -85,13 +85,13 @@ const Meeting: React.FC = () => { active: false, select: false, }, - // { - // title: '会议监控', - // icon: ImageUrl.icon48, - // iconSelect: ImageUrl.icon48Select, - // active: false, - // select: false, - // }, + { + title: '会议监控', + icon: ImageUrl.icon48, + iconSelect: ImageUrl.icon48Select, + active: false, + select: false, + }, { title: '录制', icon: ImageUrl.icon27, @@ -188,48 +188,54 @@ const Meeting: React.FC = () => { 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); + if (info.channelId === state.channelId) { + 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); + if (info.channelId === state.channelId) { + if (String(remoteUid).length === 9) { + setIsShare(remoteUid) + } else { + setTimeout(async () => { + await agora.setupRemoteVideoJoin({ + uid: Number(remoteUid), + view: document.getElementById(`video-${remoteUid}`), + channelId: 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(); + if (info.channelId === state.channelId) { + if (String(remoteUid).length === 9) { + setIsShare(null) + renderVideo() } - return res - }) + 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) => { @@ -241,11 +247,14 @@ const Meeting: React.FC = () => { }); } }) - agora.startCameraCapture() + if (state.enableCamera) { + agora.startCameraCapture() + } agora.setJoinChannel({ channelId: state.channelId, uid: userInfo.uid, token: state.token, + tokenA: userInfo.uid === '1' ? '' : state.tokenA, }) storage.setItem('noViewChatList', 0) window.addEventListener('customStorageChange', handleCustomStorageChange); @@ -465,6 +474,10 @@ const Meeting: React.FC = () => { pauseOnHover: false, }); break; + // 管理员查看随机用户 + case 'Watch': + console.log(item); + break; } }) return () => { @@ -915,7 +928,7 @@ const Meeting: React.FC = () => { break; case '会议监控': window.electron.oepnWindow({ - url: location.origin + '/#/userVideo' + url: location.origin + `/#/userVideo?channelId=${state.channelId + 'a'}&token=${state.tokenA}` }) break; } diff --git a/src/page/UserVideo/index.tsx b/src/page/UserVideo/index.tsx index ced095a..7b8dc30 100644 --- a/src/page/UserVideo/index.tsx +++ b/src/page/UserVideo/index.tsx @@ -1,10 +1,15 @@ +import { GetPolling } from '@/api/Meeting'; import styles from '@/page/UserVideo/index.module.scss' +import { storage } from '@/utils'; +import monitorAgora from '@/utils/package/monitorAgora'; import { CloseOutlined } from '@ant-design/icons'; -import { Button, Select } from 'antd'; +import { VideoSourceType } from 'agora-electron-sdk'; +import { Button, Select, message } from 'antd'; import { useEffect, useState } from "react"; - const UserVideo: React.FC = () => { + let userInfo = JSON.parse(storage.getItem('user') as string) + const [user, setUser] = useState({}); const [from, setFrom] = useState({ cycleIntervalList: [ { value: 30, label: '30秒' }, @@ -22,15 +27,86 @@ const UserVideo: React.FC = () => { ], viewPeopleValue: 4, }) + const [timeNumber, setTimeNumber] = useState(30); + const [timeStatus, setTimeStatus] = useState(false); useEffect(() => { + setUser(userInfo) + monitorAgora.init() + monitorAgora.setJoinChannel({ + channelId: getQueryParameterRegex('channelId'), + uid: userInfo.uid, + token: getQueryParameterRegex('token'), + }) + monitorAgora.registerEventHandler({ + onJoinChannelSuccess: async (info: any, _elapsed: any) => { + // await monitorAgora.setupLocalVideo({ + // uid: info.localUid, + // view: document.getElementById(`video-${info.localUid}`), + // channelId: info.channelId, + // sourceType: VideoSourceType.VideoSourceCameraPrimary, + // }) + }, + onUserJoined: async (info: any, remoteUid: any, _elapsed: any) => { + }, + onUserOffline: async (info: any, remoteUid: any, _reason: any) => { + + }, + }) + window.addEventListener('customStorageChange', handleCustomStorageChange); + return () => { + window.removeEventListener('customStorageChange', handleCustomStorageChange); + } }, []); + useEffect(() => { + let time = null as any; + if (timeStatus) { + time = setInterval(() => { + setTimeNumber((timeNumber: number) => { + if (timeNumber === 1) { + getPolling() + message.success('刷新成功!') + return from.cycleIntervalValue + } else { + return timeNumber - 1 + } + }) + }, 1000) + } else { + clearInterval(time) + } + return () => { + clearInterval(time) + } + }, [timeStatus]) + + useEffect(() => { + getPolling() + }, [from.viewPeopleValue]) + // 监听缓存变化 + const handleCustomStorageChange = async (e: any): Promise => { + // console.log(e); + }; + // 获取地址栏参数 + const getQueryParameterRegex = (name: string): string | null => { + const reg = new RegExp(`[?&]${name}=([^&#]*)`); + const results = window.location.href.match(reg); + return results === null ? null : results[1]; + } + // 获取轮训用户 + const getPolling = async (): Promise => { + GetPolling(getQueryParameterRegex('channelId') as string, from.viewPeopleValue).then((res: any) => { + console.log(res); + }) + }; return ( <>
会议监控 - + { + window.electron.closeMonitorWindow() + }} />
@@ -42,6 +118,7 @@ const UserVideo: React.FC = () => { options={from.cycleIntervalList} value={from.cycleIntervalValue} onChange={(e) => { setFrom({ ...from, cycleIntervalValue: e }) + setTimeNumber(e) }} />
@@ -55,47 +132,26 @@ const UserVideo: React.FC = () => {
- 30秒后刷新 - {/* */} - + onClick={() => { + setTimeStatus(!timeStatus) + }} + style={{ backgroundColor: '#EC3C3C' }} + >暂停循环 : + }
-
- -
-
-
-
- -
-
-
-
- -
-
-
-
- -
-
-
-
- -
-
-
-
+
diff --git a/src/render.d.ts b/src/render.d.ts index a0440e6..e0a5232 100644 --- a/src/render.d.ts +++ b/src/render.d.ts @@ -16,6 +16,8 @@ export interface IElectronAPI { quitAndInstall: (callBack: Function) => void; getVersion: () => Promise; oepnWindow: (data: any) => any; + + closeMonitorWindow: () => void } declare global { diff --git a/src/utils/package/agora.ts b/src/utils/package/agora.ts index 4cc5646..a44a65f 100644 --- a/src/utils/package/agora.ts +++ b/src/utils/package/agora.ts @@ -12,6 +12,7 @@ import { storage } from '@/utils'; const option: any = { appId: 'dcfc466a6ecb4a1f972630065dfb1e75', token: '', + tokenA: '', channelId: '', uid: '', } @@ -216,6 +217,21 @@ const agora = { } ); }, + // 所有用户加入的第二个房间 + allJoinChannelEx: async () => { + await rtcEngine.joinChannelEx( + option.tokenA, + { channelId: option.channelId + 'a', localUid: Number(option.uid) }, + { + autoSubscribeAudio: false,//设置是否自动订阅所有音频流 + autoSubscribeVideo: false,//设置是否自动订阅所有视频流 + publishMicrophoneTrack: false,//设置是否发布麦克风采集到的音频 + publishCameraTrack: true,//设置是否发布摄像头采集的视频 + clientRoleType: ClientRoleType.ClientRoleAudience,//用户角色 ClientRoleBroadcaster 主播 ClientRoleAudience 观众 + publishScreenTrack: false,//设置是否发布屏幕采集的视频 + } + ); + }, // 离开共享屏幕频道 leaveChannelEx: async (uid: any) => { await rtcEngine.leaveChannelEx({ channelId: option.channelId, localUid: Number(uid) }) @@ -250,9 +266,13 @@ const agora = { // 加入频道 setJoinChannel: async (data: any) => { option.token = data.token; + option.tokenA = data.tokenA; option.channelId = data.channelId; option.uid = Number(data.uid); await agora.joinChannel() + if (data.tokenA) { + await agora.allJoinChannelEx() + } }, // 桌面捕获音频和视频的媒体源的信息 getDesktopCapturerVideo: async () => { diff --git a/src/utils/package/features/agoraReducer.ts b/src/utils/package/features/agoraReducer.ts new file mode 100644 index 0000000..ecdcfa2 --- /dev/null +++ b/src/utils/package/features/agoraReducer.ts @@ -0,0 +1,15 @@ +// 暂时无用 +import { createSlice } from '@reduxjs/toolkit'; +export const agoraReducer = createSlice({ + name: 'agora', + initialState: { + value: 0, + } as any, + reducers: { + initAgora: async (state) => { + + }, + }, +}); +export const { initAgora } = agoraReducer.actions; +export default agoraReducer.reducer; \ No newline at end of file diff --git a/src/utils/package/monitorAgora.ts b/src/utils/package/monitorAgora.ts new file mode 100644 index 0000000..9777fcb --- /dev/null +++ b/src/utils/package/monitorAgora.ts @@ -0,0 +1,218 @@ +import { + createAgoraRtcEngine, + ClientRoleType, + VideoSourceType, + VideoViewSetupMode, + RenderModeType, +} from "agora-electron-sdk"; +import { storage } from ".."; +const option: any = { + appId: 'dcfc466a6ecb4a1f972630065dfb1e75', + token: '', + channelId: '', + uid: '', +} +let rtcEngine: any = ''; + +const monitorAgora = { + // 初始化 + init: async () => { + rtcEngine = createAgoraRtcEngine(); + await rtcEngine.initialize({ + appId: option.appId, + }); + await monitorAgora.setDeviceManager() + }, + // 获取当前设备是否存在不存在就获取默认设备 + setDeviceManager: async (bool: boolean = false) => { + const setting = await JSON.parse(storage.getItem('setting') as string) + // 摄像头 + if (setting.videoDeviceId) { + await monitorAgora.getVideoDeviceManager().then(async (res) => { + let item = res.list.find((item: any) => item.deviceId === setting.videoDeviceId); + if (item) { + await monitorAgora.setVideoDeviceManager(setting.videoDeviceId) + } else { + await monitorAgora.setVideoDeviceManager(await rtcEngine.getVideoDeviceManager().getDevice()) + setting.videoDeviceId = await rtcEngine.getVideoDeviceManager().getDevice() + } + }) + } else { + await monitorAgora.setVideoDeviceManager(await rtcEngine.getVideoDeviceManager().getDevice()) + setting.videoDeviceId = await rtcEngine.getVideoDeviceManager().getDevice() + } + + // 播放设备 + if (setting.playBackDeviceId) { + await monitorAgora.getAudioMediaList().then(async (res) => { + let item = res.playBackList.find((item: any) => item.deviceId === setting.playBackDeviceId); + if (item) { + await monitorAgora.setPlaybackDevice(setting.playBackDeviceId) + } else { + await monitorAgora.setPlaybackDevice(res.playBackItem.deviceId) + setting.playBackDeviceId = res.playBackItem.deviceId + } + }) + } else { + let deviceId = await rtcEngine.getAudioDeviceManager().getPlaybackDefaultDevice().deviceId; + await monitorAgora.setPlaybackDevice(deviceId) + setting.playBackDeviceId = deviceId + } + + // 音频设备 + if (setting.ecordingDeviceId) { + await monitorAgora.getAudioMediaList().then(async (res) => { + let item = res.ecordingList.find((item: any) => item.deviceId === setting.ecordingDeviceId); + if (item) { + await monitorAgora.setRecordingDevice(setting.ecordingDeviceId) + } else { + await monitorAgora.setRecordingDevice(res.ecordingItem.deviceId) + setting.ecordingDeviceId = res.ecordingItem.deviceId + } + }) + } else { + let deviceId = await rtcEngine.getAudioDeviceManager().getRecordingDefaultDevice().deviceId; + await monitorAgora.setRecordingDevice(deviceId) + setting.ecordingDeviceId = deviceId + } + setTimeout(async () => { + storage.setItem('setting', JSON.stringify(setting)) + if (bool) { + const setting = await JSON.parse(storage.getItem('setting') as string) + if (setting.videoDeviceId) monitorAgora.setVideoDeviceManager(setting.videoDeviceId) //指定摄像头头采集设备 + if (setting.playBackDeviceId) monitorAgora.setPlaybackDevice(setting.playBackDeviceId) //指定播放设备 + if (setting.playBackVolume) monitorAgora.setPlaybackDeviceVolume(setting.playBackVolume) // 设置播放设备音量 + if (setting.ecordingDeviceId) monitorAgora.setRecordingDevice(setting.ecordingDeviceId) // 设置音频采集设备 + if (setting.ecordingVolume) monitorAgora.setRecordingDeviceVolume(setting.ecordingVolume) // 设置音频设备音量 + } + }, 1000); + }, + // 事件回调 + registerEventHandler: ({ onJoinChannelSuccess, onUserJoined, onUserOffline }: any) => { + rtcEngine.registerEventHandler({ + // 监听本地用户加入频道事件 + onJoinChannelSuccess: async (info: any, elapsed: any) => { + await onJoinChannelSuccess(info, elapsed) + }, + // 监听远端用户加入频道事件 + onUserJoined: async (info: any, remoteUid: any, elapsed: any) => { + await onUserJoined(info, remoteUid, elapsed) + }, + // 监听用户离开频道事件 + onUserOffline: async (info: any, remoteUid: any, reason: any) => { + await onUserOffline(info, remoteUid, reason) + }, + }); + }, + // 加入频道 + setJoinChannel: async (data: any) => { + option.token = data.token; + option.channelId = data.channelId; + option.uid = Number(data.uid); + await monitorAgora.JoinChannelEx() + }, + // 所有用户加入的第二个房间 + JoinChannelEx: async () => { + await rtcEngine.startCameraCapture(VideoSourceType.VideoSourceCamera, { + format: { + width: 640, + height: 360, + fps: 15, + } + }) + await rtcEngine.joinChannelEx( + option.token, + { channelId: option.channelId, localUid: Number(option.uid) }, + { + autoSubscribeAudio: false,//设置是否自动订阅所有音频流 + autoSubscribeVideo: false,//设置是否自动订阅所有视频流 + publishMicrophoneTrack: false,//设置是否发布麦克风采集到的音频 + publishCameraTrack: true,//设置是否发布摄像头采集的视频 + clientRoleType: ClientRoleType.ClientRoleBroadcaster,//用户角色 ClientRoleBroadcaster 主播 ClientRoleAudience 观众 + publishScreenTrack: false,//设置是否发布屏幕采集的视频 + } + ); + }, + // 本地加入 + setupLocalVideo: async (item: any) => { + if (item.view?.childNodes.length === 0) { + await rtcEngine.setupLocalVideo({ + renderMode: RenderModeType.RenderModeHidden, + sourceType: item.sourceType, + uid: item.uid, + view: item.view, + setupMode: VideoViewSetupMode.VideoViewSetupAdd, + }); + } + }, + // 远端加入 + setupRemoteVideoJoin: async (item: any) => { + if (item.view?.childNodes.length === 0) { + await rtcEngine.setupRemoteVideo( + { + renderMode: RenderModeType.RenderModeHidden, + sourceType: VideoSourceType.VideoSourceRemote, + uid: item.uid, + view: item.view, + setupMode: VideoViewSetupMode.VideoViewSetupAdd, + }, + { channelId: item.channelId }, + ); + } + }, + // 退出 + setupRemoteVideo: async (item: any) => { + await rtcEngine.setupRemoteVideo( + { + renderMode: RenderModeType.RenderModeHidden, + sourceType: VideoSourceType.VideoSourceRemote, + uid: item.uid, + view: item.view, + setupMode: VideoViewSetupMode.VideoViewSetupRemove, + }, + ); + }, + // 销毁 + release: async () => { + await rtcEngine.release() + }, + // 获取系统中所有的视频设备列表。 + getVideoDeviceManager: async (): Promise => { + return { + list: rtcEngine.getVideoDeviceManager().enumerateVideoDevices(), + item: rtcEngine.getVideoDeviceManager().getDevice() + } + }, + // 通过设备 ID 指定视频采集设备。 + setVideoDeviceManager: async (deviceIdUTF8: string) => { + await rtcEngine.getVideoDeviceManager().setDevice(deviceIdUTF8) + }, + // 获取输入输出设备列表 + getAudioMediaList: async () => { + return { + playBackList: rtcEngine.getAudioDeviceManager().enumeratePlaybackDevices(), + ecordingList: rtcEngine.getAudioDeviceManager().enumerateRecordingDevices(), + playBackItem: rtcEngine.getAudioDeviceManager().getPlaybackDefaultDevice(), + ecordingItem: rtcEngine.getAudioDeviceManager().getRecordingDefaultDevice(), + ecordingVolume: rtcEngine.getAudioDeviceManager().getRecordingDeviceVolume(), + } + }, + // 指定播放设备 + setPlaybackDevice: async (deviceId: string) => { + await rtcEngine.getAudioDeviceManager().setPlaybackDevice(deviceId) + }, + // 设置音频采集设备 + setRecordingDevice: async (deviceId: string) => { + await rtcEngine.getAudioDeviceManager().setRecordingDevice(deviceId) + }, + // 设置播放设备音量 + setPlaybackDeviceVolume: async (volume: number) => { + await rtcEngine.getAudioDeviceManager().setPlaybackDeviceVolume(volume) + }, + // 设置音频设备音量 + setRecordingDeviceVolume: async (volume: number) => { + await rtcEngine.getAudioDeviceManager().setRecordingDeviceVolume(volume) + }, +} + +export default monitorAgora; \ No newline at end of file diff --git a/src/utils/package/signalr.ts b/src/utils/package/signalr.ts index 86baed7..a32b692 100644 --- a/src/utils/package/signalr.ts +++ b/src/utils/package/signalr.ts @@ -153,6 +153,13 @@ export const onSignalr = (callBack: Function) => { uname }) }); + // 管理员查看随机用户 + connection.on("Watch", (watchUids: string[]) => { + callBack({ + key: 'Watch', + watchUids + }) + }); } } export const offSignalr = () => { @@ -170,6 +177,7 @@ export const offSignalr = () => { connection.off('OperCamera'); connection.off('ManagerRefresh'); connection.off('ApplyToSpeak'); + connection.off('Watch'); } } export const onInvoke = async (str: string, data: any) => { diff --git a/src/utils/package/store.ts b/src/utils/package/store.ts new file mode 100644 index 0000000..02d1781 --- /dev/null +++ b/src/utils/package/store.ts @@ -0,0 +1,7 @@ +import { configureStore } from '@reduxjs/toolkit'; +import agoraReducer from '@/utils/package/features/agoraReducer' +export default configureStore({ + reducer: { + agora: agoraReducer, + } +}); \ No newline at end of file