diff --git a/main.js b/main.js index 54319ea..8b0f058 100644 --- a/main.js +++ b/main.js @@ -8,8 +8,8 @@ const { ipcMain, clipboard, dialog, - webFrame, Notification, + desktopCapturer, } = require('electron'); const path = require('node:path') const { autoUpdater, CancellationToken } = require('electron-updater'); @@ -180,6 +180,12 @@ app.on('ready', () => { ipcMain.handle('getIsMaximized', () => { return mainWindow.isMaximized(); }); + // 获取共享屏幕列表 + ipcMain.handle('getSources', async () => { + return await desktopCapturer.getSources({ + types: ['screen'] + }); + }); // 复制文字 ipcMain.handle('setWriteText', (event, text) => { clipboard.writeText(text) diff --git a/preload.js b/preload.js index b759da7..b6f3c3b 100644 --- a/preload.js +++ b/preload.js @@ -13,6 +13,10 @@ window.electron = { getIsMaximized: () => { return ipcRenderer.invoke('getIsMaximized') }, + // 获取共享屏幕列表 + getSources: () => { + return ipcRenderer.invoke('getSources') + }, // 复制文字 setWriteText: (text) => { return ipcRenderer.invoke('setWriteText', text) diff --git a/src/page/Meeting/index.tsx b/src/page/Meeting/index.tsx index cb077b5..2c9de5d 100644 --- a/src/page/Meeting/index.tsx +++ b/src/page/Meeting/index.tsx @@ -19,6 +19,7 @@ import { GetUserList } from '@/api/Home/User'; 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 = () => { @@ -116,6 +117,8 @@ const Meeting: React.FC = () => { const [userSearchValue, setUserSearchValue] = useState('') const [noViewChatList, setNoViewChatList] = useState(0) const [currentLookUserAccount, setCurrentLookUserAccount] = useState('') + const [recorder, setRecorder] = useState('') + const [mediaStream, setMediaStream] = useState('') const [currentLookUserStatus, setCurrentLookUserStatus] = useState<1 | 2 | 3 | 4>(1) let userInfo = JSON.parse(storage.getItem('user') as string) let allUserListArr = [] as any; @@ -269,8 +272,37 @@ const Meeting: React.FC = () => { }, []) useEffect(() => { - - }, [currentVideoId]) + if (recorder) { + const setting = JSON.parse(storage.getItem('setting') as string) + 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 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 handleNetworkChange = (): void => { @@ -427,13 +459,36 @@ const Meeting: React.FC = () => { 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) => { + const audioTracks = await navigator.mediaDevices + .getUserMedia({ audio: true, video: false }) + .then(audioStream => audioStream.getAudioTracks()[0]); + steam.addTrack(audioTracks); + 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) - // setting.recordingFilesPath } catch (error: any) { if (error.code === 'ENOENT') { message.error('文件夹不存在!') @@ -447,14 +502,29 @@ const Meeting: React.FC = () => { footerListTemplate[itemIndex][rowIndex].title = '录制' footerListTemplate[itemIndex][rowIndex].active = false setFooterList(footerListTemplate) + stopRecorderMedia() break; case '共享文件': sharedFilesModelRef.current.getData() break; } } + // 停止录制 + const stopRecorderMedia = async (): Promise => { + if (recorder) { + await recorder.stop(); + } + if (mediaStream) { + await mediaStream.getTracks().forEach((track: any) => { + track.stop() + }); + } + setRecorder('') + setMediaStream('') + } // 退出房间 const leaveChannel = async (bool?: boolean): Promise => { + await stopRecorderMedia() if (!bool) { await onInvoke('levelChannel', { roomNum: state.channelId @@ -709,7 +779,7 @@ const Meeting: React.FC = () => { ) } )} - {currentLookUserStatus === 1 ? + {currentLookUserStatus === 1 && currentLookUserAccount ?
setCurrentVideoId('1')}>
{
@@ -719,7 +789,7 @@ const Meeting: React.FC = () => { {meetingContentUser(currentLookUserAccount)} {currentLookUserAccount.enableCamera ? null : meetingContentError(currentVideoId, currentLookUserAccount)}
: null} - {currentLookUserStatus === 2 ? + {currentLookUserStatus === 2 && currentLookUserAccount ?
setCurrentVideoId('2')}>
@@ -729,7 +799,7 @@ const Meeting: React.FC = () => { {meetingContentUser(currentLookUserAccount)} {currentLookUserAccount.enableCamera ? null : meetingContentError(currentVideoId, currentLookUserAccount)}
: null} - {currentLookUserStatus === 3 ? + {currentLookUserStatus === 3 && currentLookUserAccount ?
setCurrentVideoId('3')}>
@@ -739,7 +809,7 @@ const Meeting: React.FC = () => { {meetingContentUser(currentLookUserAccount)} {currentLookUserAccount.enableCamera ? null : meetingContentError(currentVideoId, currentLookUserAccount)}
: null} - {currentLookUserStatus === 4 ? + {currentLookUserStatus === 4 && currentLookUserAccount ?
setCurrentVideoId('4')}>
diff --git a/src/render.d.ts b/src/render.d.ts index d625dac..142180c 100644 --- a/src/render.d.ts +++ b/src/render.d.ts @@ -10,6 +10,7 @@ export interface IElectronAPI { onDownload: (data: string) => void selectFilePath: () => void onFilePath: (callBack: Function) => void; + getSources: () => any; } declare global {