Compare commits

..

No commits in common. "ef2eb499ab18c128e5c5a6bf68731704cf3385a9" and "e05cda7d21c7899883e25c1feadb54eb8a6f2cee" have entirely different histories.

3 changed files with 86 additions and 196 deletions

59
main.js
View File

@ -12,7 +12,6 @@ const {
} = require('electron'); } = require('electron');
const path = require('node:path') const path = require('node:path')
const fs = require('fs'); const fs = require('fs');
const https = require('https');
const { autoUpdater, CancellationToken } = require('electron-updater'); const { autoUpdater, CancellationToken } = require('electron-updater');
const cancellationToken = new CancellationToken() const cancellationToken = new CancellationToken()
app.allowRendererProcessReuse = false; app.allowRendererProcessReuse = false;
@ -272,25 +271,6 @@ app.on('ready', () => {
const y = Math.round((display.workArea.height - mainWindow.getSize()[1]) / 2); const y = Math.round((display.workArea.height - mainWindow.getSize()[1]) / 2);
mainWindow.setPosition(x, y); mainWindow.setPosition(x, y);
}); });
// 监听渲染进程请求应用数据目录
ipcMain.handle('get-user-data-path', () => {
return app.getPath('userData');
});
// 用户数据目录路径
const userDataPath = app.getPath('userData'); // 全局变量
console.log('User Data Path:', userDataPath);
// 检查并下载 ffmpeg
checkAndDownloadFFmpeg(userDataPath)
.then(() => {
console.log('FFmpeg is ready for use.');
// 在这里执行任何依赖于 ffmpeg 的操作
})
.catch(error => {
console.error('Failed to ensure ffmpeg is available:', error);
app.quit();
});
} }
}); });
// 检测更新在你想要检查更新的时候执行renderer事件触发后的操作自行编写 // 检测更新在你想要检查更新的时候执行renderer事件触发后的操作自行编写
@ -359,42 +339,3 @@ function cancleDownloadUpdate() {
function quitAndInstall() { function quitAndInstall() {
autoUpdater.quitAndInstall(); autoUpdater.quitAndInstall();
} }
// 下载文件
function downloadFile(url, dest) {
return new Promise((resolve, reject) => {
const file = fs.createWriteStream(dest);
https.get(url, function (response) {
response.pipe(file);
file.on('finish', function () {
file.close(resolve);
});
}).on('error', function (err) {
fs.unlink(dest, () => reject(err));
});
});
}
// 检查并下载ffmpeg
function checkAndDownloadFFmpeg(appPath) {
const ffmpegPath = path.join(appPath, 'ffmpeg.exe');
return new Promise((resolve, reject) => {
if (!fs.existsSync(ffmpegPath)) {
console.log(`ffmpeg.exe not found at ${ffmpegPath}, downloading...`);
downloadFile('https://meeting-api.23544.com/meeting/update/ffmpeg.exe', ffmpegPath)
.then(() => {
console.log('ffmpeg.exe downloaded successfully.');
resolve();
})
.catch(error => {
console.error('Error downloading ffmpeg.exe:', error);
reject(error);
});
} else {
console.log(`ffmpeg.exe found at ${ffmpegPath}.`);
resolve();
}
});
}

View File

@ -1,7 +1,7 @@
{ {
"name": "WGShare.Metting", "name": "WGShare.Metting",
"private": true, "private": true,
"version": "0.3.1", "version": "0.1.14",
"main": "main.js", "main": "main.js",
"authors": "yj", "authors": "yj",
"description": "智汇享", "description": "智汇享",

View File

@ -21,10 +21,8 @@ import StupWizard from '@/components/StupWizard';
import EquipmentManagement from '@/components/EquipmentManagement'; import EquipmentManagement from '@/components/EquipmentManagement';
import UserVideo from '@/components/UserVideo'; import UserVideo from '@/components/UserVideo';
import { role } from '@/config/role'; import { role } from '@/config/role';
const { ipcRenderer } = require('electron'); import { fixWebmDuration } from "webm-duration-fix-buffer";
import * as path from 'path';
const { confirm } = Modal; const { confirm } = Modal;
const { exec } = require('child_process'); const { exec } = require('child_process');
const fs = require('fs').promises; const fs = require('fs').promises;
dayjs.extend(durationPlugin); dayjs.extend(durationPlugin);
@ -542,79 +540,32 @@ const Meeting: React.FC = () => {
useEffect(() => { useEffect(() => {
if (recorder) { if (recorder) {
recorder.start(); recorder.start();
recorder.ondataavailable = (event: any) => { recorder.ondataavailable = async (event: any) => {
const blob = new Blob([event.data], { const blob = await fixWebmDuration(event.data);
type: 'video/webm',
});
const reader = new FileReader() as any; const reader = new FileReader() as any;
reader.onload = async () => { 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 setting = await JSON.parse(storage.getItem('setting') as string)
const buffer = Buffer.from(reader.result); const buffer = Buffer.from(reader.result);
const mp4Path=`${setting.recordingFilesPath}会议录制_${state.roomName}_${state.channelId}_${formattedDate}_beforehanlder.mp4`; await fs.writeFile(`${setting.recordingFilesPath}会议录制_${state.roomName}_${state.channelId}_${+new Date()}.webm`, buffer, {});
await fs.writeFile(mp4Path, buffer); confirm({
title: '提示',
// 获取应用程序安装路径 icon: <ExclamationCircleFilled />,
const ffmpegPath = path.join(userDataPath, "ffmpeg.exe"); content: `录制成功!文件已保存至:${setting.recordingFilesPath}`,
centered: true,
const inputFilePath = mp4Path; // 输入文件路径 okText: '打开文件夹',
const outputFilePath = mp4Path.replace('_beforehanlder',''); // 输出文件路径 cancelText: '关闭',
const command = `${ffmpegPath} -i "${inputFilePath}" -vcodec copy -acodec copy "${outputFilePath}"`; async onOk() {
await fs.access(setting.recordingFilesPath, fs.constants.F_OK);
exec(command, (error:any, stdout:any, stderr:any) => { if (process.platform === 'win32') {
if (error) { exec(`explorer "${setting.recordingFilesPath}"`);
console.error('Error executing ffmpeg command:', error); } else if (process.platform === 'darwin') {
return; exec(`open "${setting.recordingFilesPath}"`);
} }
},
// 删除输入文件 onCancel() {
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 />,
content: `录制成功!文件已保存至:${setting.recordingFilesPath}`,
centered: true,
okText: '打开文件夹',
cancelText: '关闭',
async onOk() {
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}"`);
}
},
onCancel() {
}
})
});
} catch (err) {
console.error('处理录制时出错:', err);
}
}
reader.readAsArrayBuffer(blob); reader.readAsArrayBuffer(blob);
} }
}; };
@ -1065,70 +1016,70 @@ const Meeting: React.FC = () => {
} }
}) })
break; break;
case '录制': case '录制':
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);
window.electron.getSources().then(async (sources: any) => { window.electron.getSources().then(async (sources: any) => {
const screenId = sources[0].id; const screenId = sources[0].id;
const stream = await navigator.mediaDevices.getUserMedia({ const stream = await navigator.mediaDevices.getUserMedia({
audio: { audio: {
mandatory: { mandatory: {
chromeMediaSource: 'desktop', chromeMediaSource: 'desktop',
chromeMediaSourceId: screenId, chromeMediaSourceId: screenId,
} }
} as any, } as any,
video: { video: {
mandatory: { mandatory: {
chromeMediaSource: 'desktop', chromeMediaSource: 'desktop',
chromeMediaSourceId: screenId, chromeMediaSourceId: screenId,
} }
} as any } 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);
}); });
} catch (error: any) { // 获取所有音频输入设备
if (error.code === 'ENOENT') { const devices = await navigator.mediaDevices.enumerateDevices();
message.error({ const audioInputDevices = devices.filter(device => device.kind === 'audioinput' &&
content: <div> <span style={{ color: '#606fc7', cursor: 'pointer' }} onClick={() => { device.deviceId !== 'default' &&
stupWizardRef.current.changeModal(3); device.deviceId !== 'communications');
}}></span></div> // 使用Web Audio API来捕获系统声音和麦克风声音将它们合并到同一个MediaStream中。
}); const audioCtx = new (window.AudioContext || (window as any).webkitAudioContext)();
return; const systemSoundSource = audioCtx.createMediaStreamSource(stream);
} else { const systemSoundDestination = audioCtx.createMediaStreamDestination();
message.error(error); 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);
} }
}
break; break;
case '录制中': case '录制中':
footerListTemplate[itemIndex][rowIndex].title = '录制' footerListTemplate[itemIndex][rowIndex].title = '录制'
footerListTemplate[itemIndex][rowIndex].active = false footerListTemplate[itemIndex][rowIndex].active = false
@ -1334,8 +1285,6 @@ const Meeting: React.FC = () => {
} }
}) })
} }
// 开关麦克风 // 开关麦克风
const postOpenMicrApi = async (enableMicr: boolean, uid: string, isAll: boolean, isMessage: boolean = false): Promise<void> => { const postOpenMicrApi = async (enableMicr: boolean, uid: string, isAll: boolean, isMessage: boolean = false): Promise<void> => {
if (isAll) { if (isAll) {