|
|
|
|
@ -20,8 +20,8 @@ import SharedFilesModel from '@/components/SharedFilesModel';
|
|
|
|
|
import StupWizard from '@/components/StupWizard';
|
|
|
|
|
import EquipmentManagement from '@/components/EquipmentManagement';
|
|
|
|
|
import UserVideo from '@/components/UserVideo';
|
|
|
|
|
import { role } from '@/config/role';
|
|
|
|
|
const { ipcRenderer } = require('electron');
|
|
|
|
|
import { role } from '@/config/role';
|
|
|
|
|
const { ipcRenderer } = require('electron');
|
|
|
|
|
import * as path from 'path';
|
|
|
|
|
const { confirm } = Modal;
|
|
|
|
|
|
|
|
|
|
@ -549,30 +549,46 @@ const Meeting: React.FC = () => {
|
|
|
|
|
const reader = new FileReader() as any;
|
|
|
|
|
reader.onload = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const userDataPath = await ipcRenderer.invoke('get-user-data-path');
|
|
|
|
|
// 获取当前日期并格式化
|
|
|
|
|
const date = new Date();
|
|
|
|
|
const year = date.getFullYear();
|
|
|
|
|
const month = date.getMonth() + 1; // JavaScript月份从0开始
|
|
|
|
|
const day = date.getDate();
|
|
|
|
|
const hours = date.getHours();
|
|
|
|
|
const minutes = date.getMinutes();
|
|
|
|
|
const formattedDate = `${year}年${month}月${day}日${hours}时${minutes}分`;
|
|
|
|
|
const setting = await JSON.parse(storage.getItem('setting') as string)
|
|
|
|
|
const buffer = Buffer.from(reader.result);
|
|
|
|
|
const mp4Path = `${setting.recordingFilesPath}会议录制_${state.roomName}_${state.channelId}_${formattedDate}_beforehanlder.mp4`;
|
|
|
|
|
await fs.writeFile(mp4Path, buffer);
|
|
|
|
|
// 获取应用程序安装路径
|
|
|
|
|
const ffmpegPath = path.join(userDataPath, "ffmpeg.exe");
|
|
|
|
|
const userDataPath = await ipcRenderer.invoke('get-user-data-path');
|
|
|
|
|
|
|
|
|
|
// 获取当前日期并格式化
|
|
|
|
|
const date = new Date();
|
|
|
|
|
const year = date.getFullYear();
|
|
|
|
|
const month = date.getMonth() + 1; // JavaScript月份从0开始
|
|
|
|
|
const day = date.getDate();
|
|
|
|
|
const hours = date.getHours();
|
|
|
|
|
const minutes = date.getMinutes();
|
|
|
|
|
const formattedDate = `${year}年${month}月${day}日${hours}时${minutes}分`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const setting = await JSON.parse(storage.getItem('setting') as string)
|
|
|
|
|
const buffer = Buffer.from(reader.result);
|
|
|
|
|
const mp4Path=`${setting.recordingFilesPath}会议录制_${state.roomName}_${state.channelId}_${formattedDate}_beforehanlder.mp4`;
|
|
|
|
|
await fs.writeFile(mp4Path, buffer);
|
|
|
|
|
|
|
|
|
|
// 获取应用程序安装路径
|
|
|
|
|
const ffmpegPath = path.join(userDataPath, "ffmpeg.exe");
|
|
|
|
|
|
|
|
|
|
const inputFilePath = mp4Path; // 输入文件路径
|
|
|
|
|
const outputFilePath = mp4Path.replace('_beforehanlder', ''); // 输出文件路径
|
|
|
|
|
const command = `${ffmpegPath} -i "${inputFilePath}" -vcodec copy -acodec copy "${outputFilePath}"`;
|
|
|
|
|
exec(command, (error: any, _stdout: any, _stderr: any) => {
|
|
|
|
|
const outputFilePath = mp4Path.replace('_beforehanlder',''); // 输出文件路径
|
|
|
|
|
const command = `${ffmpegPath} -i "${inputFilePath}" -vcodec copy -acodec copy "${outputFilePath}"`;
|
|
|
|
|
|
|
|
|
|
exec(command, (error:any, stdout:any, stderr:any) => {
|
|
|
|
|
if (error) {
|
|
|
|
|
console.error('Error executing ffmpeg command:', error);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 删除输入文件
|
|
|
|
|
fs.unlink(inputFilePath);
|
|
|
|
|
|
|
|
|
|
// 删除输入文件
|
|
|
|
|
fs.unlink(inputFilePath, (err:any) => {
|
|
|
|
|
if (err) {
|
|
|
|
|
console.error('Error deleting input file:', err);
|
|
|
|
|
} else {
|
|
|
|
|
console.log('Input file deleted successfully.');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
confirm({
|
|
|
|
|
title: '提示',
|
|
|
|
|
icon: <ExclamationCircleFilled />,
|
|
|
|
|
@ -590,12 +606,15 @@ const Meeting: React.FC = () => {
|
|
|
|
|
},
|
|
|
|
|
onCancel() {
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
|
|
|
|
console.error('处理录制时出错:', err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reader.readAsArrayBuffer(blob);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
@ -621,6 +640,8 @@ const Meeting: React.FC = () => {
|
|
|
|
|
});
|
|
|
|
|
const observerObject = new IntersectionObserver(async (entries: IntersectionObserverEntry[], _observer: IntersectionObserver) => {
|
|
|
|
|
entries.forEach(async (entry) => {
|
|
|
|
|
console.log(entry.isIntersecting,entry.target.id);
|
|
|
|
|
|
|
|
|
|
if (entry.target.id !== user.uid) {
|
|
|
|
|
await agora.muteRemoteVideoStreamEx(Number(entry.target.id), !entry.isIntersecting)
|
|
|
|
|
}
|
|
|
|
|
@ -638,7 +659,7 @@ const Meeting: React.FC = () => {
|
|
|
|
|
});
|
|
|
|
|
observer?.disconnect();
|
|
|
|
|
}
|
|
|
|
|
}, [roomUserList, currentVideoId, footerList]);
|
|
|
|
|
}, [roomUserList, currentVideoId,footerList]);
|
|
|
|
|
|
|
|
|
|
// 声网初始化
|
|
|
|
|
const agoraInit = async () => {
|
|
|
|
|
@ -730,11 +751,11 @@ const Meeting: React.FC = () => {
|
|
|
|
|
if (res.list.length) {
|
|
|
|
|
footerListTemplate[0][1].title = item.enableCamera ? '关闭视频' : '开启视频'
|
|
|
|
|
footerListTemplate[0][1].active = !item.enableCamera
|
|
|
|
|
await agora.muteLocalVideoStream(r, item.enableMicr, item.enableCamera)
|
|
|
|
|
await agora.muteLocalVideoStream(r, item.enableMicr,item.enableCamera)
|
|
|
|
|
} else {
|
|
|
|
|
footerListTemplate[0][1].title = '开启视频'
|
|
|
|
|
footerListTemplate[0][1].active = true
|
|
|
|
|
await agora.muteLocalVideoStream(r, item.enableMicr, false)
|
|
|
|
|
await agora.muteLocalVideoStream(r, item.enableMicr,false)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
@ -743,11 +764,11 @@ const Meeting: React.FC = () => {
|
|
|
|
|
if (res.ecordingList.length) {
|
|
|
|
|
footerListTemplate[0][0].title = item.enableMicr ? '静音' : '解除静音'
|
|
|
|
|
footerListTemplate[0][0].active = !item.enableMicr
|
|
|
|
|
await agora.muteLocalAudioStream(r, item.enableMicr, item.enableCamera)
|
|
|
|
|
await agora.muteLocalAudioStream(r, item.enableMicr,item.enableCamera)
|
|
|
|
|
} else {
|
|
|
|
|
footerListTemplate[0][0].title = '解除静音'
|
|
|
|
|
footerListTemplate[0][0].active = true
|
|
|
|
|
await agora.muteLocalAudioStream(r, false, item.enableCamera)
|
|
|
|
|
await agora.muteLocalAudioStream(r, false,item.enableCamera)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
@ -1048,70 +1069,70 @@ const Meeting: React.FC = () => {
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
break;
|
|
|
|
|
case '录制':
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
window.electron.getSources().then(async (sources: any) => {
|
|
|
|
|
const screenId = sources[0].id;
|
|
|
|
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
|
|
|
audio: {
|
|
|
|
|
mandatory: {
|
|
|
|
|
chromeMediaSource: 'desktop',
|
|
|
|
|
chromeMediaSourceId: screenId,
|
|
|
|
|
}
|
|
|
|
|
} as any,
|
|
|
|
|
video: {
|
|
|
|
|
mandatory: {
|
|
|
|
|
chromeMediaSource: 'desktop',
|
|
|
|
|
chromeMediaSourceId: screenId,
|
|
|
|
|
}
|
|
|
|
|
} as any
|
|
|
|
|
case '录制':
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
window.electron.getSources().then(async (sources: any) => {
|
|
|
|
|
const screenId = sources[0].id;
|
|
|
|
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
|
|
|
audio: {
|
|
|
|
|
mandatory: {
|
|
|
|
|
chromeMediaSource: 'desktop',
|
|
|
|
|
chromeMediaSourceId: screenId,
|
|
|
|
|
}
|
|
|
|
|
} as any,
|
|
|
|
|
video: {
|
|
|
|
|
mandatory: {
|
|
|
|
|
chromeMediaSource: 'desktop',
|
|
|
|
|
chromeMediaSourceId: screenId,
|
|
|
|
|
}
|
|
|
|
|
} as any
|
|
|
|
|
});
|
|
|
|
|
// 获取所有音频输入设备
|
|
|
|
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
|
|
|
const audioInputDevices = devices.filter(device => device.kind === 'audioinput' &&
|
|
|
|
|
device.deviceId !== 'default' &&
|
|
|
|
|
device.deviceId !== 'communications');
|
|
|
|
|
// 使用Web Audio API来捕获系统声音和麦克风声音,将它们合并到同一个MediaStream中。
|
|
|
|
|
const audioCtx = new (window.AudioContext || (window as any).webkitAudioContext)();
|
|
|
|
|
const systemSoundSource = audioCtx.createMediaStreamSource(stream);
|
|
|
|
|
const systemSoundDestination = audioCtx.createMediaStreamDestination();
|
|
|
|
|
systemSoundSource.connect(systemSoundDestination);
|
|
|
|
|
// 录制所有音频输入设备
|
|
|
|
|
audioInputDevices.forEach(async device => {
|
|
|
|
|
const micStream = await navigator.mediaDevices.getUserMedia({ audio: { deviceId: { exact: device.deviceId } } });
|
|
|
|
|
setMediaStream(micStream);
|
|
|
|
|
const micSoundSource = audioCtx.createMediaStreamSource(micStream);
|
|
|
|
|
micSoundSource.connect(systemSoundDestination);
|
|
|
|
|
})
|
|
|
|
|
// 合并音频流与视频流
|
|
|
|
|
const combinedSource = new MediaStream([...stream.getVideoTracks(), ...systemSoundDestination.stream.getAudioTracks()]);
|
|
|
|
|
// 开始录制
|
|
|
|
|
const mediaRecorder = new MediaRecorder(combinedSource, {
|
|
|
|
|
mimeType: 'video/webm;codecs=vp9,opus',
|
|
|
|
|
videoBitsPerSecond: 1.5e6,
|
|
|
|
|
});
|
|
|
|
|
setRecorder(mediaRecorder);
|
|
|
|
|
});
|
|
|
|
|
// 获取所有音频输入设备
|
|
|
|
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
|
|
|
const audioInputDevices = devices.filter(device => device.kind === 'audioinput' &&
|
|
|
|
|
device.deviceId !== 'default' &&
|
|
|
|
|
device.deviceId !== 'communications');
|
|
|
|
|
// 使用Web Audio API来捕获系统声音和麦克风声音,将它们合并到同一个MediaStream中。
|
|
|
|
|
const audioCtx = new (window.AudioContext || (window as any).webkitAudioContext)();
|
|
|
|
|
const systemSoundSource = audioCtx.createMediaStreamSource(stream);
|
|
|
|
|
const systemSoundDestination = audioCtx.createMediaStreamDestination();
|
|
|
|
|
systemSoundSource.connect(systemSoundDestination);
|
|
|
|
|
// 录制所有音频输入设备
|
|
|
|
|
audioInputDevices.forEach(async device => {
|
|
|
|
|
const micStream = await navigator.mediaDevices.getUserMedia({ audio: { deviceId: { exact: device.deviceId } } });
|
|
|
|
|
setMediaStream(micStream);
|
|
|
|
|
const micSoundSource = audioCtx.createMediaStreamSource(micStream);
|
|
|
|
|
micSoundSource.connect(systemSoundDestination);
|
|
|
|
|
})
|
|
|
|
|
// 合并音频流与视频流
|
|
|
|
|
const combinedSource = new MediaStream([...stream.getVideoTracks(), ...systemSoundDestination.stream.getAudioTracks()]);
|
|
|
|
|
// 开始录制
|
|
|
|
|
const mediaRecorder = new MediaRecorder(combinedSource, {
|
|
|
|
|
mimeType: 'video/webm;codecs=vp9,opus',
|
|
|
|
|
videoBitsPerSecond: 1.5e6,
|
|
|
|
|
});
|
|
|
|
|
setRecorder(mediaRecorder);
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
if (error.code === 'ENOENT') {
|
|
|
|
|
message.error({
|
|
|
|
|
content: <div>文件夹不存在 <span style={{ color: '#606fc7', cursor: 'pointer' }} onClick={() => {
|
|
|
|
|
stupWizardRef.current.changeModal(3);
|
|
|
|
|
}}>前往设置</span></div>
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
} else {
|
|
|
|
|
message.error(error);
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
if (error.code === 'ENOENT') {
|
|
|
|
|
message.error({
|
|
|
|
|
content: <div>文件夹不存在 <span style={{ color: '#606fc7', cursor: 'pointer' }} onClick={() => {
|
|
|
|
|
stupWizardRef.current.changeModal(3);
|
|
|
|
|
}}>前往设置</span></div>
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
} else {
|
|
|
|
|
message.error(error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
case '录制中':
|
|
|
|
|
footerListTemplate[itemIndex][rowIndex].title = '录制'
|
|
|
|
|
footerListTemplate[itemIndex][rowIndex].active = false
|
|
|
|
|
@ -1317,7 +1338,7 @@ const Meeting: React.FC = () => {
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 开关麦克风
|
|
|
|
|
const postOpenMicrApi = async (enableMicr: boolean, uid: string, isAll: boolean, isMessage: boolean = false): Promise<void> => {
|
|
|
|
|
|