录制优化
This commit is contained in:
parent
998f7f781a
commit
0c5365ccb1
8
main.js
8
main.js
|
|
@ -8,8 +8,8 @@ const {
|
||||||
ipcMain,
|
ipcMain,
|
||||||
clipboard,
|
clipboard,
|
||||||
dialog,
|
dialog,
|
||||||
webFrame,
|
|
||||||
Notification,
|
Notification,
|
||||||
|
desktopCapturer,
|
||||||
} = require('electron');
|
} = require('electron');
|
||||||
const path = require('node:path')
|
const path = require('node:path')
|
||||||
const { autoUpdater, CancellationToken } = require('electron-updater');
|
const { autoUpdater, CancellationToken } = require('electron-updater');
|
||||||
|
|
@ -180,6 +180,12 @@ app.on('ready', () => {
|
||||||
ipcMain.handle('getIsMaximized', () => {
|
ipcMain.handle('getIsMaximized', () => {
|
||||||
return mainWindow.isMaximized();
|
return mainWindow.isMaximized();
|
||||||
});
|
});
|
||||||
|
// 获取共享屏幕列表
|
||||||
|
ipcMain.handle('getSources', async () => {
|
||||||
|
return await desktopCapturer.getSources({
|
||||||
|
types: ['screen']
|
||||||
|
});
|
||||||
|
});
|
||||||
// 复制文字
|
// 复制文字
|
||||||
ipcMain.handle('setWriteText', (event, text) => {
|
ipcMain.handle('setWriteText', (event, text) => {
|
||||||
clipboard.writeText(text)
|
clipboard.writeText(text)
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,10 @@ window.electron = {
|
||||||
getIsMaximized: () => {
|
getIsMaximized: () => {
|
||||||
return ipcRenderer.invoke('getIsMaximized')
|
return ipcRenderer.invoke('getIsMaximized')
|
||||||
},
|
},
|
||||||
|
// 获取共享屏幕列表
|
||||||
|
getSources: () => {
|
||||||
|
return ipcRenderer.invoke('getSources')
|
||||||
|
},
|
||||||
// 复制文字
|
// 复制文字
|
||||||
setWriteText: (text) => {
|
setWriteText: (text) => {
|
||||||
return ipcRenderer.invoke('setWriteText', text)
|
return ipcRenderer.invoke('setWriteText', text)
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import { GetUserList } from '@/api/Home/User';
|
||||||
import Avatar from '@/components/Avatar';
|
import Avatar from '@/components/Avatar';
|
||||||
import SharedFilesModel from '@/components/SharedFilesModel';
|
import SharedFilesModel from '@/components/SharedFilesModel';
|
||||||
import StupWizard from '@/components/StupWizard';
|
import StupWizard from '@/components/StupWizard';
|
||||||
|
const { exec } = require('child_process');
|
||||||
const fs = require('fs').promises;
|
const fs = require('fs').promises;
|
||||||
dayjs.extend(durationPlugin);
|
dayjs.extend(durationPlugin);
|
||||||
const Meeting: React.FC = () => {
|
const Meeting: React.FC = () => {
|
||||||
|
|
@ -116,6 +117,8 @@ const Meeting: React.FC = () => {
|
||||||
const [userSearchValue, setUserSearchValue] = useState('')
|
const [userSearchValue, setUserSearchValue] = useState('')
|
||||||
const [noViewChatList, setNoViewChatList] = useState(0)
|
const [noViewChatList, setNoViewChatList] = useState(0)
|
||||||
const [currentLookUserAccount, setCurrentLookUserAccount] = useState<any>('')
|
const [currentLookUserAccount, setCurrentLookUserAccount] = useState<any>('')
|
||||||
|
const [recorder, setRecorder] = useState<any>('')
|
||||||
|
const [mediaStream, setMediaStream] = useState<any>('')
|
||||||
const [currentLookUserStatus, setCurrentLookUserStatus] = useState<1 | 2 | 3 | 4>(1)
|
const [currentLookUserStatus, setCurrentLookUserStatus] = useState<1 | 2 | 3 | 4>(1)
|
||||||
let userInfo = JSON.parse(storage.getItem('user') as string)
|
let userInfo = JSON.parse(storage.getItem('user') as string)
|
||||||
let allUserListArr = [] as any;
|
let allUserListArr = [] as any;
|
||||||
|
|
@ -269,8 +272,37 @@ const Meeting: React.FC = () => {
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (recorder) {
|
||||||
}, [currentVideoId])
|
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 => {
|
const handleNetworkChange = (): void => {
|
||||||
|
|
@ -427,13 +459,36 @@ const Meeting: React.FC = () => {
|
||||||
invitingPersonnelRef.current.changeInvitingPersonnelModal()
|
invitingPersonnelRef.current.changeInvitingPersonnelModal()
|
||||||
break;
|
break;
|
||||||
case '录制':
|
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)
|
const setting = await JSON.parse(storage.getItem('setting') as string)
|
||||||
try {
|
try {
|
||||||
await fs.access(setting.recordingFilesPath, fs.constants.F_OK)
|
await fs.access(setting.recordingFilesPath, fs.constants.F_OK)
|
||||||
footerListTemplate[itemIndex][rowIndex].title = '录制中'
|
footerListTemplate[itemIndex][rowIndex].title = '录制中'
|
||||||
footerListTemplate[itemIndex][rowIndex].active = true
|
footerListTemplate[itemIndex][rowIndex].active = true
|
||||||
setFooterList(footerListTemplate)
|
setFooterList(footerListTemplate)
|
||||||
// setting.recordingFilesPath
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.code === 'ENOENT') {
|
if (error.code === 'ENOENT') {
|
||||||
message.error('文件夹不存在!')
|
message.error('文件夹不存在!')
|
||||||
|
|
@ -447,14 +502,29 @@ const Meeting: React.FC = () => {
|
||||||
footerListTemplate[itemIndex][rowIndex].title = '录制'
|
footerListTemplate[itemIndex][rowIndex].title = '录制'
|
||||||
footerListTemplate[itemIndex][rowIndex].active = false
|
footerListTemplate[itemIndex][rowIndex].active = false
|
||||||
setFooterList(footerListTemplate)
|
setFooterList(footerListTemplate)
|
||||||
|
stopRecorderMedia()
|
||||||
break;
|
break;
|
||||||
case '共享文件':
|
case '共享文件':
|
||||||
sharedFilesModelRef.current.getData()
|
sharedFilesModelRef.current.getData()
|
||||||
break;
|
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): Promise<void> => {
|
const leaveChannel = async (bool?: boolean): Promise<void> => {
|
||||||
|
await stopRecorderMedia()
|
||||||
if (!bool) {
|
if (!bool) {
|
||||||
await onInvoke('levelChannel', {
|
await onInvoke('levelChannel', {
|
||||||
roomNum: state.channelId
|
roomNum: state.channelId
|
||||||
|
|
@ -709,7 +779,7 @@ const Meeting: React.FC = () => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
{currentLookUserStatus === 1 ?
|
{currentLookUserStatus === 1 && currentLookUserAccount ?
|
||||||
<div className={`${styles.meetingContentSwiperCard} ${setMeetingContentSwiperCardClass('1')}`} onClick={() => setCurrentVideoId('1')}>
|
<div className={`${styles.meetingContentSwiperCard} ${setMeetingContentSwiperCardClass('1')}`} onClick={() => setCurrentVideoId('1')}>
|
||||||
<div className={`${styles.meetingContentSwiperCardVdeio}`} id='video-source-camera-primary'>
|
<div className={`${styles.meetingContentSwiperCardVdeio}`} id='video-source-camera-primary'>
|
||||||
{<div className={styles.meetingContentSwiperCardVdeioLoading}>
|
{<div className={styles.meetingContentSwiperCardVdeioLoading}>
|
||||||
|
|
@ -719,7 +789,7 @@ const Meeting: React.FC = () => {
|
||||||
{meetingContentUser(currentLookUserAccount)}
|
{meetingContentUser(currentLookUserAccount)}
|
||||||
{currentLookUserAccount.enableCamera ? null : meetingContentError(currentVideoId, currentLookUserAccount)}
|
{currentLookUserAccount.enableCamera ? null : meetingContentError(currentVideoId, currentLookUserAccount)}
|
||||||
</div> : null}
|
</div> : null}
|
||||||
{currentLookUserStatus === 2 ?
|
{currentLookUserStatus === 2 && currentLookUserAccount ?
|
||||||
<div className={`${styles.meetingContentSwiperCard} ${setMeetingContentSwiperCardClass('2')}`} onClick={() => setCurrentVideoId('2')}>
|
<div className={`${styles.meetingContentSwiperCard} ${setMeetingContentSwiperCardClass('2')}`} onClick={() => setCurrentVideoId('2')}>
|
||||||
<div className={`${styles.meetingContentSwiperCardVdeio}`} id='video-source-screen'>
|
<div className={`${styles.meetingContentSwiperCardVdeio}`} id='video-source-screen'>
|
||||||
<div className={styles.meetingContentSwiperCardVdeioLoading}>
|
<div className={styles.meetingContentSwiperCardVdeioLoading}>
|
||||||
|
|
@ -729,7 +799,7 @@ const Meeting: React.FC = () => {
|
||||||
{meetingContentUser(currentLookUserAccount)}
|
{meetingContentUser(currentLookUserAccount)}
|
||||||
{currentLookUserAccount.enableCamera ? null : meetingContentError(currentVideoId, currentLookUserAccount)}
|
{currentLookUserAccount.enableCamera ? null : meetingContentError(currentVideoId, currentLookUserAccount)}
|
||||||
</div> : null}
|
</div> : null}
|
||||||
{currentLookUserStatus === 3 ?
|
{currentLookUserStatus === 3 && currentLookUserAccount ?
|
||||||
<div className={`${styles.meetingContentSwiperCard} ${setMeetingContentSwiperCardClass('3')}`} onClick={() => setCurrentVideoId('3')}>
|
<div className={`${styles.meetingContentSwiperCard} ${setMeetingContentSwiperCardClass('3')}`} onClick={() => setCurrentVideoId('3')}>
|
||||||
<div className={`${styles.meetingContentSwiperCardVdeio}`} id='video-source-remote-camera'>
|
<div className={`${styles.meetingContentSwiperCardVdeio}`} id='video-source-remote-camera'>
|
||||||
<div className={styles.meetingContentSwiperCardVdeioLoading}>
|
<div className={styles.meetingContentSwiperCardVdeioLoading}>
|
||||||
|
|
@ -739,7 +809,7 @@ const Meeting: React.FC = () => {
|
||||||
{meetingContentUser(currentLookUserAccount)}
|
{meetingContentUser(currentLookUserAccount)}
|
||||||
{currentLookUserAccount.enableCamera ? null : meetingContentError(currentVideoId, currentLookUserAccount)}
|
{currentLookUserAccount.enableCamera ? null : meetingContentError(currentVideoId, currentLookUserAccount)}
|
||||||
</div> : null}
|
</div> : null}
|
||||||
{currentLookUserStatus === 4 ?
|
{currentLookUserStatus === 4 && currentLookUserAccount ?
|
||||||
<div className={`${styles.meetingContentSwiperCard} ${setMeetingContentSwiperCardClass('4')}`} onClick={() => setCurrentVideoId('4')}>
|
<div className={`${styles.meetingContentSwiperCard} ${setMeetingContentSwiperCardClass('4')}`} onClick={() => setCurrentVideoId('4')}>
|
||||||
<div className={`${styles.meetingContentSwiperCardVdeio}`} id='video-source-remote-screen'>
|
<div className={`${styles.meetingContentSwiperCardVdeio}`} id='video-source-remote-screen'>
|
||||||
<div className={styles.meetingContentSwiperCardVdeioLoading}>
|
<div className={styles.meetingContentSwiperCardVdeioLoading}>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ export interface IElectronAPI {
|
||||||
onDownload: (data: string) => void
|
onDownload: (data: string) => void
|
||||||
selectFilePath: () => void
|
selectFilePath: () => void
|
||||||
onFilePath: (callBack: Function) => void;
|
onFilePath: (callBack: Function) => void;
|
||||||
|
getSources: () => any;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue