diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index 729408f..261d93f 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -359,10 +359,37 @@ export function registerIpcHandlers( onRecordingStateChange?: (recording: boolean, sourceName: string) => void, switchToHud?: () => void, ) { + const supportsWindowOpacity = process.platform !== "linux"; const countdownOverlayState = { visible: false, value: null as number | null, activeRunId: null as number | null, + hideCommitId: 0, + hideCommitTimer: null as ReturnType | null, + }; + const COUNTDOWN_OVERLAY_HIDE_DEBOUNCE_MS = 1200; + + const clearCountdownOverlayHideCommit = () => { + if (countdownOverlayState.hideCommitTimer) { + clearTimeout(countdownOverlayState.hideCommitTimer); + countdownOverlayState.hideCommitTimer = null; + } + }; + + const commitCountdownOverlayHide = (win: BrowserWindow, hideCommitId: number) => { + if (win.isDestroyed()) { + return; + } + + if (countdownOverlayState.visible || countdownOverlayState.hideCommitId !== hideCommitId) { + return; + } + + win.hide(); + if (supportsWindowOpacity) { + // Reset baseline opacity for the next show cycle. + win.setOpacity(1); + } }; const flushCountdownOverlayState = (win: BrowserWindow) => { @@ -370,14 +397,35 @@ export function registerIpcHandlers( return; } + clearCountdownOverlayHideCommit(); win.webContents.send("countdown-overlay-value", countdownOverlayState.value); - if (countdownOverlayState.visible && !win.isVisible()) { - setTimeout(() => { - if (!win.isDestroyed() && countdownOverlayState.visible && !win.isVisible()) { - win.showInactive(); - } - }, 16); + if (!countdownOverlayState.visible) { + return; } + + if (win.isVisible()) { + if (supportsWindowOpacity) { + win.setOpacity(1); + } + return; + } + + setTimeout(() => { + if (!win.isDestroyed() && countdownOverlayState.visible && !win.isVisible()) { + if (supportsWindowOpacity) { + win.setOpacity(0); + } + win.showInactive(); + + if (supportsWindowOpacity) { + setTimeout(() => { + if (!win.isDestroyed() && countdownOverlayState.visible && win.isVisible()) { + win.setOpacity(1); + } + }, 0); + } + } + }, 16); }; ipcMain.handle("countdown-overlay-show", (_, value: number, runId: number) => { @@ -426,16 +474,35 @@ export function registerIpcHandlers( } countdownOverlayState.visible = false; - countdownOverlayState.value = null; + countdownOverlayState.hideCommitId += 1; + const hideCommitId = countdownOverlayState.hideCommitId; + clearCountdownOverlayHideCommit(); const win = getCountdownOverlayWindow(); if (!win || win.isDestroyed()) { + countdownOverlayState.value = null; return; } + if (supportsWindowOpacity) { + // Hide visually immediately to avoid hide/show compositor flashes on rapid restart. + win.setOpacity(0); + } + + countdownOverlayState.value = null; if (!win.webContents.isLoading()) { win.webContents.send("countdown-overlay-value", countdownOverlayState.value); } + + if (!supportsWindowOpacity) { + win.hide(); + return; + } + + countdownOverlayState.hideCommitTimer = setTimeout(() => { + countdownOverlayState.hideCommitTimer = null; + commitCountdownOverlayHide(win, hideCommitId); + }, COUNTDOWN_OVERLAY_HIDE_DEBOUNCE_MS); }); ipcMain.handle("switch-to-hud", () => { diff --git a/src/components/launch/CountdownOverlay.tsx b/src/components/launch/CountdownOverlay.tsx index 54ec8e8..71d12c5 100644 --- a/src/components/launch/CountdownOverlay.tsx +++ b/src/components/launch/CountdownOverlay.tsx @@ -11,18 +11,20 @@ export function CountdownOverlay() { return () => unsubscribe(); }, []); + if (value === null) { + return null; + } + return (
- {value === null ? null : ( -
-
- {value} -
+
+
+ {value}
- )} +
); } diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index 7c9112d..6644f42 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -339,9 +339,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { const activeRunId = countdownRunId.current; if (cleanup) cleanup(); countdownRunId.current += 1; - void window.electronAPI.hideCountdownOverlay(activeRunId).catch((error) => { - console.warn("Failed to hide countdown overlay during cleanup:", error); - }); + void safeHideCountdownOverlay(activeRunId); allowAutoFinalize.current = false; restarting.current = false; discardRecordingId.current = null; @@ -635,6 +633,11 @@ export function useScreenRecorder(): UseScreenRecorderReturn { ); } + if (!isCountdownRunActive(countdownRunToken)) { + teardownMedia(); + return; + } + let { width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT,