Skip to content
This repository was archived by the owner on Jun 17, 2026. It is now read-only.

Commit c10441f

Browse files
committed
fix:flickering, stale runs, macOS bugs provided by coderabbit and thread countdown token
1 parent 6526f82 commit c10441f

7 files changed

Lines changed: 91 additions & 18 deletions

File tree

electron/electron-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ interface Window {
138138
showCountdownOverlay: (value: number) => Promise<void>;
139139
setCountdownOverlayValue: (value: number) => Promise<void>;
140140
hideCountdownOverlay: () => Promise<void>;
141-
onCountdownOverlayValue: (callback: (value: number) => void) => () => void;
141+
onCountdownOverlayValue: (callback: (value: number | null) => void) => () => void;
142142
setMicrophoneExpanded: (expanded: boolean) => void;
143143
setHasUnsavedChanges: (hasChanges: boolean) => void;
144144
onRequestSaveBeforeClose: (callback: () => Promise<boolean> | boolean) => () => void;

electron/ipc/handlers.ts

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,30 @@ export function registerIpcHandlers(
359359
onRecordingStateChange?: (recording: boolean, sourceName: string) => void,
360360
switchToHud?: () => void,
361361
) {
362-
ipcMain.handle("countdown-overlay-show", async (_, value: number) => {
362+
const countdownOverlayState = {
363+
visible: false,
364+
value: null as number | null,
365+
};
366+
367+
const flushCountdownOverlayState = (win: BrowserWindow) => {
368+
if (win.isDestroyed()) {
369+
return;
370+
}
371+
372+
win.webContents.send("countdown-overlay-value", countdownOverlayState.value);
373+
if (countdownOverlayState.visible && !win.isVisible()) {
374+
setTimeout(() => {
375+
if (!win.isDestroyed() && countdownOverlayState.visible && !win.isVisible()) {
376+
win.showInactive();
377+
}
378+
}, 16);
379+
}
380+
};
381+
382+
ipcMain.handle("countdown-overlay-show", (_, value: number) => {
383+
countdownOverlayState.visible = true;
384+
countdownOverlayState.value = value;
385+
363386
const win = getCountdownOverlayWindow() ?? createCountdownOverlayWindow();
364387
if (win.isDestroyed()) {
365388
return;
@@ -368,38 +391,47 @@ export function registerIpcHandlers(
368391
if (win.webContents.isLoading()) {
369392
win.webContents.once("did-finish-load", () => {
370393
if (!win.isDestroyed()) {
371-
win.webContents.send("countdown-overlay-value", value);
372-
win.showInactive();
394+
flushCountdownOverlayState(win);
373395
}
374396
});
375397
} else {
376-
win.webContents.send("countdown-overlay-value", value);
377-
win.showInactive();
398+
flushCountdownOverlayState(win);
378399
}
379400
});
380401

381402
ipcMain.handle("countdown-overlay-set-value", (_, value: number) => {
403+
countdownOverlayState.value = value;
404+
382405
const win = getCountdownOverlayWindow();
383406
if (!win || win.isDestroyed()) {
384407
return;
385408
}
386409

410+
if (win.webContents.isLoading()) {
411+
return;
412+
}
413+
387414
win.webContents.send("countdown-overlay-value", value);
388415
});
389416

390417
ipcMain.handle("countdown-overlay-hide", () => {
418+
countdownOverlayState.visible = false;
419+
countdownOverlayState.value = null;
420+
391421
const win = getCountdownOverlayWindow();
392422
if (!win || win.isDestroyed()) {
393423
return;
394424
}
395425

396-
win.hide();
426+
if (!win.webContents.isLoading()) {
427+
win.webContents.send("countdown-overlay-value", countdownOverlayState.value);
428+
}
397429
});
398430

399431
ipcMain.handle("switch-to-hud", () => {
400432
if (switchToHud) switchToHud();
401433
});
402-
ipcMain.handle("start-new-recording", async () => {
434+
ipcMain.handle("start-new-recording", () => {
403435
try {
404436
setCurrentRecordingSessionState(null);
405437
if (switchToHud) {

electron/main.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -349,8 +349,17 @@ app.on("window-all-closed", () => {
349349
app.on("activate", () => {
350350
// On OS X it's common to re-create a window in the app when the
351351
// dock icon is clicked and there are no other windows open.
352-
if (BrowserWindow.getAllWindows().length === 0) {
353-
createWindow();
352+
const hasVisibleWindow = BrowserWindow.getAllWindows().some((window) => {
353+
if (window.isDestroyed() || !window.isVisible()) {
354+
return false;
355+
}
356+
357+
const url = window.webContents.getURL();
358+
const isCountdownOverlayWindow = url.includes("windowType=countdown-overlay");
359+
return !isCountdownOverlayWindow;
360+
});
361+
if (!hasVisibleWindow) {
362+
showMainWindow();
354363
}
355364
});
356365

electron/preload.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,8 @@ contextBridge.exposeInMainWorld("electronAPI", {
139139
hideCountdownOverlay: () => {
140140
return ipcRenderer.invoke("countdown-overlay-hide");
141141
},
142-
onCountdownOverlayValue: (callback: (value: number) => void) => {
143-
const listener = (_event: unknown, value: number) => callback(value);
142+
onCountdownOverlayValue: (callback: (value: number | null) => void) => {
143+
const listener = (_event: unknown, value: number | null) => callback(value);
144144
ipcRenderer.on("countdown-overlay-value", listener);
145145
return () => ipcRenderer.removeListener("countdown-overlay-value", listener);
146146
},

electron/windows.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ export function createCountdownOverlayWindow(): BrowserWindow {
204204
transparent: true,
205205
backgroundColor: "#00000000",
206206
hasShadow: false,
207-
show: !HEADLESS,
207+
show: false,
208208
webPreferences: {
209209
preload: path.join(__dirname, "preload.mjs"),
210210
nodeIntegration: false,

src/components/launch/CountdownOverlay.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useEffect, useState } from "react";
22

33
export function CountdownOverlay() {
4-
const [value, setValue] = useState(3);
4+
const [value, setValue] = useState<number | null>(null);
55

66
useEffect(() => {
77
const unsubscribe = window.electronAPI.onCountdownOverlayValue((nextValue) => {
@@ -13,7 +13,11 @@ export function CountdownOverlay() {
1313

1414
return (
1515
<div className="w-screen h-screen bg-transparent flex items-center justify-center pointer-events-none select-none">
16-
<div className="text-white/90 text-[120px] font-bold leading-none tabular-nums">{value}</div>
16+
{value === null ? null : (
17+
<div className="text-white/90 text-[120px] font-bold leading-none tabular-nums">
18+
{value}
19+
</div>
20+
)}
1721
</div>
1822
);
1923
}

src/hooks/useScreenRecorder.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,9 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
401401
}
402402
};
403403

404+
const isCountdownRunActive = (runId?: number) =>
405+
runId === undefined || countdownRunId.current === runId;
406+
404407
const startRecordCountdown = async () => {
405408
if (countdownActive || recording) {
406409
return;
@@ -442,23 +445,28 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
442445
return;
443446
}
444447

445-
await startRecording();
448+
await startRecording(runId);
446449
} finally {
447450
if (countdownRunId.current === runId) {
448451
setCountdownActive(false);
452+
await safeHideCountdownOverlay();
449453
}
450-
await safeHideCountdownOverlay();
451454
}
452455
};
453456

454-
const startRecording = async () => {
457+
const startRecording = async (countdownRunToken?: number) => {
455458
try {
456459
const selectedSource = await window.electronAPI.getSelectedSource();
457460
if (!selectedSource) {
458461
alert(t("recording.selectSource"));
459462
return;
460463
}
461464

465+
if (!isCountdownRunActive(countdownRunToken)) {
466+
teardownMedia();
467+
return;
468+
}
469+
462470
let screenMediaStream: MediaStream;
463471

464472
const videoConstraints = {
@@ -499,6 +507,11 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
499507
}
500508
screenStream.current = screenMediaStream;
501509

510+
if (!isCountdownRunActive(countdownRunToken)) {
511+
teardownMedia();
512+
return;
513+
}
514+
502515
if (microphoneEnabled) {
503516
try {
504517
microphoneStream.current = await navigator.mediaDevices.getUserMedia({
@@ -523,6 +536,11 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
523536
}
524537
}
525538

539+
if (!isCountdownRunActive(countdownRunToken)) {
540+
teardownMedia();
541+
return;
542+
}
543+
526544
if (webcamEnabled) {
527545
try {
528546
webcamStream.current = await navigator.mediaDevices.getUserMedia({
@@ -551,6 +569,11 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
551569
}
552570
}
553571

572+
if (!isCountdownRunActive(countdownRunToken)) {
573+
teardownMedia();
574+
return;
575+
}
576+
554577
stream.current = new MediaStream();
555578
const videoTrack = screenMediaStream.getVideoTracks()[0];
556579
if (!videoTrack) {
@@ -610,6 +633,11 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
610633
);
611634

612635
const hasAudio = stream.current.getAudioTracks().length > 0;
636+
if (!isCountdownRunActive(countdownRunToken)) {
637+
teardownMedia();
638+
return;
639+
}
640+
613641
screenRecorder.current = createRecorderHandle(stream.current, {
614642
mimeType,
615643
videoBitsPerSecond,

0 commit comments

Comments
 (0)