feat: v1.2.0 — auto-reconnect, recording, monitor bounds, WASAPI loopback
- Auto-reconnect: stream retries with exponential backoff (2s→30s max) on unexpected exit; stops only when user clicks Stop - Recording: global RecordingBar with folder picker, segment duration (min), per-stream checkbox, Select All; uses FFmpeg tee muxer for simultaneous SRT + segmented .ts file output - Monitor capture: PowerShell System.Windows.Forms.Screen gives exact pixel bounds (x/y/width/height); per-monitor deviceName is now unique (desktop_monitor_N); gdigrab uses -offset_x/-offset_y/-video_size - Window capture: windowTitle now explicitly propagated in handleVideoSelect - System audio: added WASAPI Loopback option (no VB-Audio required), existing virtual-audio-capturer kept as fallback - Reconnecting event forwarded to renderer; status shows "Reconnecting…" with attempt count in logs - IPC: pick-folder (dialog.showOpenDialog) and open-folder (shell.openPath) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+22
-7
@@ -1,4 +1,4 @@
|
||||
const { app, BrowserWindow, ipcMain, Tray, Menu, nativeImage, dialog } = require('electron')
|
||||
const { app, BrowserWindow, ipcMain, Tray, Menu, nativeImage, dialog, shell } = require('electron')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const ffmpegManager = require('./ffmpeg')
|
||||
@@ -124,7 +124,7 @@ function loadConfig() {
|
||||
} catch (e) {
|
||||
console.error('Failed to load config:', e)
|
||||
}
|
||||
return { streams: [], tolbek: { enabled: false, port: 5000, latency: 200 } }
|
||||
return { streams: [], tolbek: { enabled: false, port: 5000, latency: 200 }, recording: { folder: '', segmentMinutes: 60 } }
|
||||
}
|
||||
|
||||
function saveConfig(config) {
|
||||
@@ -323,6 +323,19 @@ ipcMain.handle('get-active-streams', () => {
|
||||
return ffmpegManager.getActiveStreams()
|
||||
})
|
||||
|
||||
ipcMain.handle('pick-folder', async () => {
|
||||
const result = await dialog.showOpenDialog(mainWindow || undefined, {
|
||||
title: 'Выберите папку для записи',
|
||||
properties: ['openDirectory', 'createDirectory']
|
||||
})
|
||||
if (result.canceled || !result.filePaths.length) return null
|
||||
return result.filePaths[0]
|
||||
})
|
||||
|
||||
ipcMain.handle('open-folder', async (_, folderPath) => {
|
||||
try { await shell.openPath(folderPath) } catch {}
|
||||
})
|
||||
|
||||
ipcMain.handle('minimize-window', () => {
|
||||
mainWindow && mainWindow.minimize()
|
||||
})
|
||||
@@ -335,21 +348,23 @@ ipcMain.on('update-tray-count', (_, count) => {
|
||||
updateTrayMenu(count)
|
||||
})
|
||||
|
||||
// Forward FFmpeg log events to renderer
|
||||
// Forward FFmpeg events to renderer
|
||||
ffmpegManager.on('log', (data) => {
|
||||
mainWindow && mainWindow.webContents.send('stream-log', data)
|
||||
})
|
||||
|
||||
ffmpegManager.on('error', (data) => {
|
||||
mainWindow && mainWindow.webContents.send('stream-error', data)
|
||||
const active = ffmpegManager.getActiveCount()
|
||||
updateTrayMenu(active)
|
||||
updateTrayMenu(ffmpegManager.getActiveCount())
|
||||
})
|
||||
|
||||
ffmpegManager.on('ended', (data) => {
|
||||
mainWindow && mainWindow.webContents.send('stream-ended', data)
|
||||
const active = ffmpegManager.getActiveCount()
|
||||
updateTrayMenu(active)
|
||||
updateTrayMenu(ffmpegManager.getActiveCount())
|
||||
})
|
||||
|
||||
ffmpegManager.on('reconnecting', (data) => {
|
||||
mainWindow && mainWindow.webContents.send('stream-reconnecting', data)
|
||||
})
|
||||
|
||||
app.whenReady().then(() => {
|
||||
|
||||
Reference in New Issue
Block a user