Fixed an issue where clicking wouldn't line up with screenshot part 4
Template tests / tests (push) Successful in 1m48s
Template tests / tests (push) Successful in 1m48s
This commit is contained in:
+8
-4
@@ -25,9 +25,9 @@ const { encodePng } = require('../core/png');
|
|||||||
// Dedupe duplicate watcher events for one physical click while still
|
// Dedupe duplicate watcher events for one physical click while still
|
||||||
// allowing intentionally fast clicking.
|
// allowing intentionally fast clicking.
|
||||||
const CLICK_DEBOUNCE_MS = 40;
|
const CLICK_DEBOUNCE_MS = 40;
|
||||||
// Idle gap between frame-loop grabs; the effective refresh rate is
|
// Idle gap between frame-loop grabs. Keep this at zero so the buffered
|
||||||
// grab-duration + this.
|
// frame stays as close to real time as possible while recording.
|
||||||
const FRAME_LOOP_IDLE_MS = 50;
|
const FRAME_LOOP_IDLE_MS = 0;
|
||||||
// A buffered frame older than this is too stale to pass off as "the screen
|
// A buffered frame older than this is too stale to pass off as "the screen
|
||||||
// at the instant of the click".
|
// at the instant of the click".
|
||||||
const CLICK_FRAME_MAX_AGE_MS = 600;
|
const CLICK_FRAME_MAX_AGE_MS = 600;
|
||||||
@@ -68,6 +68,7 @@ class CaptureService {
|
|||||||
this.latestFrame = null;
|
this.latestFrame = null;
|
||||||
this.lastClickCapture = 0;
|
this.lastClickCapture = 0;
|
||||||
this.clickWatcherButtonDown = false;
|
this.clickWatcherButtonDown = false;
|
||||||
|
this.frameLoopInFlight = false;
|
||||||
this.shooting = false;
|
this.shooting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,10 +333,12 @@ class CaptureService {
|
|||||||
if (!this.frameLoopRunning) return;
|
if (!this.frameLoopRunning) return;
|
||||||
if (!this.session || this.session.paused) {
|
if (!this.session || this.session.paused) {
|
||||||
this.frameLoopRunning = false;
|
this.frameLoopRunning = false;
|
||||||
|
this.frameLoopInFlight = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (!this.shooting) {
|
if (!this.shooting) {
|
||||||
|
this.frameLoopInFlight = true;
|
||||||
const mode = this.settings.get('capture.mode') || 'fullscreen';
|
const mode = this.settings.get('capture.mode') || 'fullscreen';
|
||||||
const grabMode = mode === 'region' ? 'fullscreen' : mode;
|
const grabMode = mode === 'region' ? 'fullscreen' : mode;
|
||||||
const frame = await this.captureCurrentFrame(grabMode);
|
const frame = await this.captureCurrentFrame(grabMode);
|
||||||
@@ -344,6 +347,7 @@ class CaptureService {
|
|||||||
} catch {
|
} catch {
|
||||||
// Grab failures are fine — clicks fall back to a one-off fresh shot.
|
// Grab failures are fine — clicks fall back to a one-off fresh shot.
|
||||||
} finally {
|
} finally {
|
||||||
|
this.frameLoopInFlight = false;
|
||||||
if (this.frameLoopRunning && this.session && !this.session.paused) {
|
if (this.frameLoopRunning && this.session && !this.session.paused) {
|
||||||
this.frameLoopTimer = setTimeout(tick, FRAME_LOOP_IDLE_MS);
|
this.frameLoopTimer = setTimeout(tick, FRAME_LOOP_IDLE_MS);
|
||||||
}
|
}
|
||||||
@@ -405,7 +409,7 @@ class CaptureService {
|
|||||||
&& sameDisplay;
|
&& sameDisplay;
|
||||||
};
|
};
|
||||||
if (usable(this.latestFrame)) return this.latestFrame;
|
if (usable(this.latestFrame)) return this.latestFrame;
|
||||||
if (!this.frameLoopRunning) return null;
|
if (!this.frameLoopRunning || !this.frameLoopInFlight) return null;
|
||||||
const deadline = Date.now() + CLICK_FRAME_WAIT_MS;
|
const deadline = Date.now() + CLICK_FRAME_WAIT_MS;
|
||||||
while (this.frameLoopRunning && Date.now() < deadline) {
|
while (this.frameLoopRunning && Date.now() < deadline) {
|
||||||
const next = await this.nextFrame(Math.max(1, deadline - Date.now()));
|
const next = await this.nextFrame(Math.max(1, deadline - Date.now()));
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ test('a buffered frame from a different display is ignored for click capture', a
|
|||||||
const service = makeService();
|
const service = makeService();
|
||||||
service.session = { guideId: 'guide-display', paused: false, count: 0, intervalSec: 0 };
|
service.session = { guideId: 'guide-display', paused: false, count: 0, intervalSec: 0 };
|
||||||
service.frameLoopRunning = true;
|
service.frameLoopRunning = true;
|
||||||
|
service.frameLoopInFlight = true;
|
||||||
service.latestFrame = makeFrame('wrong-display', 0, {
|
service.latestFrame = makeFrame('wrong-display', 0, {
|
||||||
display: { bounds: { x: 0, y: 0, width: 100, height: 100 } },
|
display: { bounds: { x: 0, y: 0, width: 100, height: 100 } },
|
||||||
});
|
});
|
||||||
@@ -156,10 +157,36 @@ test('a stale buffered frame is not reused — the click falls back to a fresh s
|
|||||||
assert.equal(shootCalled, true, 'a stale buffered frame must not be reused');
|
assert.equal(shootCalled, true, 'a stale buffered frame must not be reused');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('an idle click capture does not wait for the next frame loop tick', async () => {
|
||||||
|
const service = makeService();
|
||||||
|
service.session = { guideId: 'guide-idle', paused: false, count: 0, intervalSec: 0 };
|
||||||
|
service.frameLoopRunning = true;
|
||||||
|
service.frameLoopInFlight = false;
|
||||||
|
|
||||||
|
let nextFrameCalled = false;
|
||||||
|
service.nextFrame = async () => {
|
||||||
|
nextFrameCalled = true;
|
||||||
|
throw new Error('idle clicks must not wait for a new frame');
|
||||||
|
};
|
||||||
|
|
||||||
|
let shootCalled = false;
|
||||||
|
service.shoot = async () => {
|
||||||
|
shootCalled = true;
|
||||||
|
return { ok: true, step: { stepId: 'idle-step' } };
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await service.sessionCapture('click', { x: 1, y: 1 });
|
||||||
|
|
||||||
|
assert.equal(result.ok, true);
|
||||||
|
assert.equal(shootCalled, true);
|
||||||
|
assert.equal(nextFrameCalled, false);
|
||||||
|
});
|
||||||
|
|
||||||
test('clicks during an in-flight grab wait for the frame instead of being dropped', async () => {
|
test('clicks during an in-flight grab wait for the frame instead of being dropped', async () => {
|
||||||
const service = makeService();
|
const service = makeService();
|
||||||
service.session = { guideId: 'guide-fast', paused: false, count: 0, intervalSec: 0 };
|
service.session = { guideId: 'guide-fast', paused: false, count: 0, intervalSec: 0 };
|
||||||
service.frameLoopRunning = true; // a grab is in flight, no frame buffered yet
|
service.frameLoopRunning = true; // a grab is in flight, no frame buffered yet
|
||||||
|
service.frameLoopInFlight = true;
|
||||||
service.shoot = async () => {
|
service.shoot = async () => {
|
||||||
throw new Error('waiting clicks must use the loop frame, not a competing shot');
|
throw new Error('waiting clicks must use the loop frame, not a competing shot');
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user