Fix capture degrading in eco/power-saving mode; decouple from display refresh rate
Template tests / tests (push) Successful in 1m47s
Template tests / tests (push) Successful in 1m47s
Two changes:
- Hold powerSaveBlocker.start('prevent-app-suspension') for the duration of
each recording session. Windows Power Throttling (EcoQoS) can CPU-starve
the capture-worker renderer mid-session, causing stream frame requests to
time out and degrade to the slower in-process legacy loop. The blocker
signals to the OS that this is an active workload and keeps the stream
worker and main-process event loop running at full speed. Released on
pause and finish.
- Remove maxFrameRate: 30 from the getUserMedia constraint in the capture
worker. The actual sampling rate is driven entirely by the setInterval
timer, so the stream's native frame rate is irrelevant to capture
correctness. Capping at 30 could silently reduce stream delivery on some
drivers when the display itself runs at ≤30 Hz (common in power-saving
mode), leaving the ring buffer with fewer fresh frames at click time.
This commit is contained in:
+28
-6
@@ -5,7 +5,7 @@ const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const {
|
||||
app, BrowserWindow, ipcMain, dialog, shell, nativeTheme, globalShortcut,
|
||||
clipboard, nativeImage, screen,
|
||||
clipboard, nativeImage, screen, powerSaveBlocker,
|
||||
} = require('electron');
|
||||
|
||||
const { GuideStore } = require('../core/store');
|
||||
@@ -498,12 +498,34 @@ function setupIpc() {
|
||||
if (result.ok) reindex(guideId);
|
||||
return result;
|
||||
});
|
||||
let capturePowerBlocker = -1;
|
||||
const startCapturePower = () => {
|
||||
if (!powerSaveBlocker.isStarted(capturePowerBlocker)) {
|
||||
capturePowerBlocker = powerSaveBlocker.start('prevent-app-suspension');
|
||||
}
|
||||
};
|
||||
const stopCapturePower = () => {
|
||||
if (powerSaveBlocker.isStarted(capturePowerBlocker)) {
|
||||
powerSaveBlocker.stop(capturePowerBlocker);
|
||||
}
|
||||
};
|
||||
|
||||
h('capture:session', async ({ action, guideId, intervalSec }) => {
|
||||
if (action === 'start') capture.startSession(guideId, { intervalSec: intervalSec ?? null });
|
||||
else if (action === 'pause') capture.togglePause(true);
|
||||
else if (action === 'resume') capture.togglePause(false);
|
||||
else if (action === 'finish') capture.finishSession();
|
||||
else if (action === 'interval') capture.setInterval(intervalSec);
|
||||
if (action === 'start') {
|
||||
capture.startSession(guideId, { intervalSec: intervalSec ?? null });
|
||||
startCapturePower();
|
||||
} else if (action === 'pause') {
|
||||
capture.togglePause(true);
|
||||
stopCapturePower();
|
||||
} else if (action === 'resume') {
|
||||
capture.togglePause(false);
|
||||
startCapturePower();
|
||||
} else if (action === 'finish') {
|
||||
capture.finishSession();
|
||||
stopCapturePower();
|
||||
} else if (action === 'interval') {
|
||||
capture.setInterval(intervalSec);
|
||||
}
|
||||
const state = capture.state();
|
||||
sendToRenderer('capture:state', state);
|
||||
return state;
|
||||
|
||||
@@ -80,7 +80,9 @@
|
||||
maxWidth: physWidth,
|
||||
minHeight: physHeight,
|
||||
maxHeight: physHeight,
|
||||
maxFrameRate: 30,
|
||||
// No maxFrameRate: sampling cadence is controlled by the setInterval
|
||||
// timer below, so the actual capture rate is always sampleMs-driven
|
||||
// regardless of display refresh rate or power mode.
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user