fix:flickering, stale runs, macOS bugs provided by coderabbit and thread countdown token

This commit is contained in:
Galactic99
2026-04-16 18:27:00 +05:30
parent 1670db41a8
commit 6b08a0a72a
7 changed files with 91 additions and 18 deletions
+1 -1
View File
@@ -138,7 +138,7 @@ interface Window {
showCountdownOverlay: (value: number) => Promise<void>;
setCountdownOverlayValue: (value: number) => Promise<void>;
hideCountdownOverlay: () => Promise<void>;
onCountdownOverlayValue: (callback: (value: number) => void) => () => void;
onCountdownOverlayValue: (callback: (value: number | null) => void) => () => void;
setMicrophoneExpanded: (expanded: boolean) => void;
setHasUnsavedChanges: (hasChanges: boolean) => void;
onRequestSaveBeforeClose: (callback: () => Promise<boolean> | boolean) => () => void;
+39 -7
View File
@@ -359,7 +359,30 @@ export function registerIpcHandlers(
onRecordingStateChange?: (recording: boolean, sourceName: string) => void,
switchToHud?: () => void,
) {
ipcMain.handle("countdown-overlay-show", async (_, value: number) => {
const countdownOverlayState = {
visible: false,
value: null as number | null,
};
const flushCountdownOverlayState = (win: BrowserWindow) => {
if (win.isDestroyed()) {
return;
}
win.webContents.send("countdown-overlay-value", countdownOverlayState.value);
if (countdownOverlayState.visible && !win.isVisible()) {
setTimeout(() => {
if (!win.isDestroyed() && countdownOverlayState.visible && !win.isVisible()) {
win.showInactive();
}
}, 16);
}
};
ipcMain.handle("countdown-overlay-show", (_, value: number) => {
countdownOverlayState.visible = true;
countdownOverlayState.value = value;
const win = getCountdownOverlayWindow() ?? createCountdownOverlayWindow();
if (win.isDestroyed()) {
return;
@@ -368,38 +391,47 @@ export function registerIpcHandlers(
if (win.webContents.isLoading()) {
win.webContents.once("did-finish-load", () => {
if (!win.isDestroyed()) {
win.webContents.send("countdown-overlay-value", value);
win.showInactive();
flushCountdownOverlayState(win);
}
});
} else {
win.webContents.send("countdown-overlay-value", value);
win.showInactive();
flushCountdownOverlayState(win);
}
});
ipcMain.handle("countdown-overlay-set-value", (_, value: number) => {
countdownOverlayState.value = value;
const win = getCountdownOverlayWindow();
if (!win || win.isDestroyed()) {
return;
}
if (win.webContents.isLoading()) {
return;
}
win.webContents.send("countdown-overlay-value", value);
});
ipcMain.handle("countdown-overlay-hide", () => {
countdownOverlayState.visible = false;
countdownOverlayState.value = null;
const win = getCountdownOverlayWindow();
if (!win || win.isDestroyed()) {
return;
}
win.hide();
if (!win.webContents.isLoading()) {
win.webContents.send("countdown-overlay-value", countdownOverlayState.value);
}
});
ipcMain.handle("switch-to-hud", () => {
if (switchToHud) switchToHud();
});
ipcMain.handle("start-new-recording", async () => {
ipcMain.handle("start-new-recording", () => {
try {
setCurrentRecordingSessionState(null);
if (switchToHud) {
+11 -2
View File
@@ -349,8 +349,17 @@ app.on("window-all-closed", () => {
app.on("activate", () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
const hasVisibleWindow = BrowserWindow.getAllWindows().some((window) => {
if (window.isDestroyed() || !window.isVisible()) {
return false;
}
const url = window.webContents.getURL();
const isCountdownOverlayWindow = url.includes("windowType=countdown-overlay");
return !isCountdownOverlayWindow;
});
if (!hasVisibleWindow) {
showMainWindow();
}
});
+2 -2
View File
@@ -139,8 +139,8 @@ contextBridge.exposeInMainWorld("electronAPI", {
hideCountdownOverlay: () => {
return ipcRenderer.invoke("countdown-overlay-hide");
},
onCountdownOverlayValue: (callback: (value: number) => void) => {
const listener = (_event: unknown, value: number) => callback(value);
onCountdownOverlayValue: (callback: (value: number | null) => void) => {
const listener = (_event: unknown, value: number | null) => callback(value);
ipcRenderer.on("countdown-overlay-value", listener);
return () => ipcRenderer.removeListener("countdown-overlay-value", listener);
},
+1 -1
View File
@@ -204,7 +204,7 @@ export function createCountdownOverlayWindow(): BrowserWindow {
transparent: true,
backgroundColor: "#00000000",
hasShadow: false,
show: !HEADLESS,
show: false,
webPreferences: {
preload: path.join(__dirname, "preload.mjs"),
nodeIntegration: false,
+6 -2
View File
@@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
export function CountdownOverlay() {
const [value, setValue] = useState(3);
const [value, setValue] = useState<number | null>(null);
useEffect(() => {
const unsubscribe = window.electronAPI.onCountdownOverlayValue((nextValue) => {
@@ -13,7 +13,11 @@ export function CountdownOverlay() {
return (
<div className="w-screen h-screen bg-transparent flex items-center justify-center pointer-events-none select-none">
<div className="text-white/90 text-[120px] font-bold leading-none tabular-nums">{value}</div>
{value === null ? null : (
<div className="text-white/90 text-[120px] font-bold leading-none tabular-nums">
{value}
</div>
)}
</div>
);
}
+31 -3
View File
@@ -401,6 +401,9 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
}
};
const isCountdownRunActive = (runId?: number) =>
runId === undefined || countdownRunId.current === runId;
const startRecordCountdown = async () => {
if (countdownActive || recording) {
return;
@@ -442,16 +445,16 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
return;
}
await startRecording();
await startRecording(runId);
} finally {
if (countdownRunId.current === runId) {
setCountdownActive(false);
await safeHideCountdownOverlay();
}
await safeHideCountdownOverlay();
}
};
const startRecording = async () => {
const startRecording = async (countdownRunToken?: number) => {
try {
const selectedSource = await window.electronAPI.getSelectedSource();
if (!selectedSource) {
@@ -459,6 +462,11 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
return;
}
if (!isCountdownRunActive(countdownRunToken)) {
teardownMedia();
return;
}
let screenMediaStream: MediaStream;
const videoConstraints = {
@@ -499,6 +507,11 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
}
screenStream.current = screenMediaStream;
if (!isCountdownRunActive(countdownRunToken)) {
teardownMedia();
return;
}
if (microphoneEnabled) {
try {
microphoneStream.current = await navigator.mediaDevices.getUserMedia({
@@ -523,6 +536,11 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
}
}
if (!isCountdownRunActive(countdownRunToken)) {
teardownMedia();
return;
}
if (webcamEnabled) {
try {
webcamStream.current = await navigator.mediaDevices.getUserMedia({
@@ -551,6 +569,11 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
}
}
if (!isCountdownRunActive(countdownRunToken)) {
teardownMedia();
return;
}
stream.current = new MediaStream();
const videoTrack = screenMediaStream.getVideoTracks()[0];
if (!videoTrack) {
@@ -610,6 +633,11 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
);
const hasAudio = stream.current.getAudioTracks().length > 0;
if (!isCountdownRunActive(countdownRunToken)) {
teardownMedia();
return;
}
screenRecorder.current = createRecorderHandle(stream.current, {
mimeType,
videoBitsPerSecond,