From 7fb4c5de9e8b7b37a63ad1dffb86cd971e888868 Mon Sep 17 00:00:00 2001 From: webadderall <131426131+webadderall@users.noreply.github.com> Date: Fri, 27 Mar 2026 11:30:11 +1100 Subject: [PATCH] Revert "Fix WGC display selection and webcam sync" This reverts commit e5becc30a3bd3f1dab2f165f150ecec1d70aec3f. --- electron/electron-env.d.ts | 3 +- electron/ipc/handlers.ts | 105 ++++-------------- electron/native/wgc-capture/src/main.cpp | 4 +- .../native/wgc-capture/src/monitor_utils.cpp | 10 +- .../native/wgc-capture/src/monitor_utils.h | 3 +- electron/preload.ts | 2 +- src/components/video-editor/VideoEditor.tsx | 11 +- src/components/video-editor/VideoPlayback.tsx | 2 +- .../video-editor/projectPersistence.ts | 4 - src/components/video-editor/types.ts | 3 - src/hooks/useScreenRecorder.ts | 38 +------ 11 files changed, 46 insertions(+), 139 deletions(-) diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts index 1f570cd8..7b8876e0 100644 --- a/electron/electron-env.d.ts +++ b/electron/electron-env.d.ts @@ -183,11 +183,10 @@ interface Window { setCurrentRecordingSession: (session: { videoPath: string; webcamPath?: string | null; - timeOffsetMs?: number; }) => Promise<{ success: boolean }>; getCurrentRecordingSession: () => Promise<{ success: boolean; - session?: { videoPath: string; webcamPath?: string | null; timeOffsetMs?: number }; + session?: { videoPath: string; webcamPath?: string | null }; }>; getCurrentVideoPath: () => Promise<{ success: boolean; path?: string }>; clearCurrentVideoPath: () => Promise<{ success: boolean }>; diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index e422fc47..e239762e 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -81,14 +81,12 @@ type WindowBounds = { type RecordingSessionData = { videoPath: string webcamPath?: string | null - timeOffsetMs?: number } type RecordingSessionManifest = { - version: 1 | 2 + version: 1 videoFileName: string webcamFileName?: string | null - timeOffsetMs?: number } type ProjectLibraryEntry = { @@ -386,7 +384,6 @@ async function loadProjectFromPath(projectPath: string) { currentRecordingSession = { videoPath: mediaSources.videoPath, webcamPath: mediaSources.webcamPath, - timeOffsetMs: mediaSources.timeOffsetMs, } await rememberRecentProject(normalizedPath) @@ -423,7 +420,6 @@ async function resolveProjectMediaSources(project: unknown): Promise< success: true videoPath: string webcamPath: string | null - timeOffsetMs: number } | { success: false @@ -457,9 +453,6 @@ async function resolveProjectMediaSources(project: unknown): Promise< typeof (project as { editor?: { webcam?: { sourcePath?: unknown } } }).editor?.webcam?.sourcePath === 'string' ? ((project as { editor?: { webcam?: { sourcePath?: string } } }).editor?.webcam?.sourcePath ?? null) : null - const timeOffsetMs = normalizeRecordingTimeOffsetMs( - (project as { editor?: { webcam?: { timeOffsetMs?: unknown } } }).editor?.webcam?.timeOffsetMs, - ) const normalizedWebcamPath = normalizeVideoSourcePath(rawWebcamPath) if (!normalizedWebcamPath) { @@ -467,7 +460,6 @@ async function resolveProjectMediaSources(project: unknown): Promise< success: true, videoPath: normalizedVideoPath, webcamPath: null, - timeOffsetMs, } } @@ -477,24 +469,16 @@ async function resolveProjectMediaSources(project: unknown): Promise< success: true, videoPath: normalizedVideoPath, webcamPath: normalizedWebcamPath, - timeOffsetMs, } } catch { return { success: true, videoPath: normalizedVideoPath, webcamPath: null, - timeOffsetMs, } } } -function normalizeRecordingTimeOffsetMs(value: unknown): number { - return typeof value === 'number' && Number.isFinite(value) - ? Math.round(value) - : 0 -} - function getRecordingSessionManifestPath(videoPath: string) { const extension = path.extname(videoPath) const baseName = path.basename(videoPath, extension) @@ -516,10 +500,9 @@ async function persistRecordingSessionManifest(session: RecordingSessionData): P } const manifest: RecordingSessionManifest = { - version: 2, + version: 1, videoFileName: path.basename(normalizedVideoPath), webcamFileName: path.basename(normalizedWebcamPath), - timeOffsetMs: normalizeRecordingTimeOffsetMs(session.timeOffsetMs), } await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8') @@ -536,7 +519,7 @@ async function resolveRecordingSessionManifest(videoPath?: string | null): Promi try { const content = await fs.readFile(manifestPath, 'utf-8') const parsed = JSON.parse(content) as Partial - if (parsed.version !== 1 && parsed.version !== 2) { + if (parsed.version !== 1) { return null } @@ -548,7 +531,6 @@ async function resolveRecordingSessionManifest(videoPath?: string | null): Promi return { videoPath: normalizedVideoPath, webcamPath: null, - timeOffsetMs: 0, } } @@ -558,7 +540,6 @@ async function resolveRecordingSessionManifest(videoPath?: string | null): Promi return { videoPath: normalizedVideoPath, webcamPath, - timeOffsetMs: normalizeRecordingTimeOffsetMs(parsed.timeOffsetMs), } } catch { return null @@ -613,7 +594,6 @@ async function resolveRecordingSession(videoPath?: string | null): Promise((resolve, reject) => { const timer = setTimeout(() => { @@ -2498,7 +2445,6 @@ function snapshotCursorTelemetryForPersistence() { } async function finalizeStoredVideo(videoPath: string) { - await validateRecordedVideoFile(videoPath) snapshotCursorTelemetryForPersistence() currentVideoPath = videoPath currentProjectPath = null @@ -3061,9 +3007,6 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh} const micPath = path.join(recordingsDir, `recording-${timestamp}.mic.wav`) config.captureMic = true config.micOutputPath = micPath - if (options.microphoneDeviceId) { - config.micDeviceId = options.microphoneDeviceId - } if (options.microphoneLabel) { config.micDeviceName = options.microphoneLabel } @@ -3264,7 +3207,6 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh} await moveFileWithOverwrite(tempVideoPath, finalVideoPath) } - await validateRecordedVideoFile(finalVideoPath) windowsPendingVideoPath = finalVideoPath return { success: true, path: finalVideoPath } } catch (error) { @@ -3331,13 +3273,14 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh} await moveFileWithOverwrite(tempVideoPath, finalVideoPath) } - const requiresAudio = Boolean(preferredSystemAudioPath || preferredMicrophonePath) - - if (requiresAudio) { - await muxNativeMacRecordingWithAudio(finalVideoPath, preferredSystemAudioPath, preferredMicrophonePath) + if (preferredSystemAudioPath || preferredMicrophonePath) { + try { + await muxNativeMacRecordingWithAudio(finalVideoPath, preferredSystemAudioPath, preferredMicrophonePath) + } catch (error) { + console.warn('Failed to mux native macOS audio into capture:', error) + } } - await validateRecordedVideoFile(finalVideoPath, { requiresAudio }) return await finalizeStoredVideo(finalVideoPath) } catch (error) { console.error('Failed to stop native ScreenCaptureKit recording:', error) @@ -3471,21 +3414,22 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh} } try { - const requiresAudio = Boolean(windowsSystemAudioPath || windowsMicAudioPath) - - if (requiresAudio) { + if (windowsSystemAudioPath || windowsMicAudioPath) { await muxNativeWindowsVideoWithAudio(videoPath, windowsSystemAudioPath, windowsMicAudioPath) windowsSystemAudioPath = null windowsMicAudioPath = null } - await validateRecordedVideoFile(videoPath, { requiresAudio }) return await finalizeStoredVideo(videoPath) } catch (error) { console.error('Failed to mux native Windows recording:', error) windowsSystemAudioPath = null windowsMicAudioPath = null - return { success: false, message: 'Failed to mux native Windows recording', error: String(error) } + try { + return await finalizeStoredVideo(videoPath) + } catch { + return { success: false, message: 'Failed to mux native Windows recording', error: String(error) } + } } }) @@ -4309,13 +4253,12 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh} return { success: true, webcamPath: resolvedSession.webcamPath ?? null } }) - ipcMain.handle('set-current-recording-session', async (_, session: { videoPath: string; webcamPath?: string | null; timeOffsetMs?: number }) => { + ipcMain.handle('set-current-recording-session', async (_, session: { videoPath: string; webcamPath?: string | null }) => { const normalizedVideoPath = normalizeVideoSourcePath(session.videoPath) ?? session.videoPath currentVideoPath = normalizedVideoPath currentRecordingSession = { videoPath: normalizedVideoPath, webcamPath: normalizeVideoSourcePath(session.webcamPath ?? null), - timeOffsetMs: normalizeRecordingTimeOffsetMs(session.timeOffsetMs), } currentProjectPath = null await persistRecordingSessionManifest(currentRecordingSession) @@ -4362,6 +4305,10 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh} } }); + ipcMain.handle('app:getVersion', () => { + return app.getVersion() + }) + ipcMain.handle('get-platform', () => { return process.platform; }); @@ -4498,9 +4445,5 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh} seconds: countdownInProgress ? countdownRemaining : null, } }) - - ipcMain.handle('app:getVersion', () => { - return app.getVersion() - }) } diff --git a/electron/native/wgc-capture/src/main.cpp b/electron/native/wgc-capture/src/main.cpp index 9bbaed33..697b1ac0 100644 --- a/electron/native/wgc-capture/src/main.cpp +++ b/electron/native/wgc-capture/src/main.cpp @@ -24,7 +24,7 @@ static std::mutex g_stopMutex; static std::condition_variable g_stopCv; struct CaptureConfig { - int64_t displayId = 0; + int displayId = 0; int64_t windowHandle = 0; std::string outputPath; std::string audioOutputPath; @@ -97,7 +97,7 @@ static bool parseSimpleJson(const std::string& json, CaptureConfig& config) { config.outputPath = findString("outputPath"); if (config.outputPath.empty()) return false; - int64_t displayId = findInt64("displayId"); + int displayId = findInt("displayId"); if (displayId >= 0) config.displayId = displayId; int64_t windowHandle = findInt64("windowHandle"); diff --git a/electron/native/wgc-capture/src/monitor_utils.cpp b/electron/native/wgc-capture/src/monitor_utils.cpp index e6b0d03d..25203ebf 100644 --- a/electron/native/wgc-capture/src/monitor_utils.cpp +++ b/electron/native/wgc-capture/src/monitor_utils.cpp @@ -27,16 +27,20 @@ std::vector enumerateMonitors() { } // Electron uses the HMONITOR handle value cast to a number as the display ID. -HMONITOR findMonitorByDisplayId(int64_t displayId) { +HMONITOR findMonitorByDisplayId(int displayId) { auto monitors = enumerateMonitors(); for (const auto& m : monitors) { - if (static_cast(reinterpret_cast(m.handle)) == displayId) { + if (static_cast(reinterpret_cast(m.handle)) == displayId) { return m.handle; } } - return nullptr; + if (!monitors.empty()) { + return monitors[0].handle; + } + + return MonitorFromPoint({0, 0}, MONITOR_DEFAULTTOPRIMARY); } MonitorInfo getMonitorInfo(HMONITOR monitor) { diff --git a/electron/native/wgc-capture/src/monitor_utils.h b/electron/native/wgc-capture/src/monitor_utils.h index 8889410d..513e3105 100644 --- a/electron/native/wgc-capture/src/monitor_utils.h +++ b/electron/native/wgc-capture/src/monitor_utils.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -15,5 +14,5 @@ struct MonitorInfo { }; std::vector enumerateMonitors(); -HMONITOR findMonitorByDisplayId(int64_t displayId); +HMONITOR findMonitorByDisplayId(int displayId); MonitorInfo getMonitorInfo(HMONITOR monitor); diff --git a/electron/preload.ts b/electron/preload.ts index 638511d0..cfe9d8f4 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -194,7 +194,7 @@ contextBridge.exposeInMainWorld("electronAPI", { setCurrentVideoPath: (path: string) => { return ipcRenderer.invoke("set-current-video-path", path); }, - setCurrentRecordingSession: (session: { videoPath: string; webcamPath?: string | null; timeOffsetMs?: number }) => { + setCurrentRecordingSession: (session: { videoPath: string; webcamPath?: string | null }) => { return ipcRenderer.invoke("set-current-recording-session", session); }, getCurrentRecordingSession: () => { diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index 77da916d..dc11eec5 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -1175,7 +1175,7 @@ export default function VideoEditor() { }, [currentPersistedEditorState, currentSourcePath]); const syncRecordingSessionWebcam = useCallback( - async (webcamPath: string | null, timeOffsetMs = 0) => { + async (webcamPath: string | null) => { if (!currentSourcePath || !window.electronAPI.setCurrentRecordingSession) { return; } @@ -1183,7 +1183,6 @@ export default function VideoEditor() { await window.electronAPI.setCurrentRecordingSession({ videoPath: currentSourcePath, webcamPath, - timeOffsetMs, }); }, [currentSourcePath], @@ -1214,10 +1213,9 @@ export default function VideoEditor() { ...prev, enabled: true, sourcePath: result.path ?? null, - timeOffsetMs: 0, })); - await syncRecordingSessionWebcam(result.path, 0); + await syncRecordingSessionWebcam(result.path); toast.success(t("settings.effects.webcamFootageAdded")); }, [syncRecordingSessionWebcam, t]); @@ -1226,10 +1224,9 @@ export default function VideoEditor() { ...prev, enabled: false, sourcePath: null, - timeOffsetMs: 0, })); - await syncRecordingSessionWebcam(null, 0); + await syncRecordingSessionWebcam(null); toast.success(t("settings.effects.webcamFootageRemoved")); }, [syncRecordingSessionWebcam, t]); @@ -1298,7 +1295,6 @@ export default function VideoEditor() { ...prev, enabled: Boolean(sessionResult.session?.webcamPath), sourcePath: sessionResult.session?.webcamPath ?? null, - timeOffsetMs: sessionResult.session?.timeOffsetMs ?? 0, })); return; } @@ -1314,7 +1310,6 @@ export default function VideoEditor() { ...prev, enabled: false, sourcePath: null, - timeOffsetMs: 0, })); } else { setError("No video to load. Please record or select a video."); diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx index 6ba75ac0..0e8fe195 100644 --- a/src/components/video-editor/VideoPlayback.tsx +++ b/src/components/video-editor/VideoPlayback.tsx @@ -945,7 +945,7 @@ const VideoPlayback = forwardRef( } const targetTime = clampMediaTimeToDuration( - Math.max(0, currentTime - (webcam.timeOffsetMs ?? 0) / 1000), + currentTime, Number.isFinite(webcamVideo.duration) ? webcamVideo.duration : null, ); diff --git a/src/components/video-editor/projectPersistence.ts b/src/components/video-editor/projectPersistence.ts index b8a1c500..7720b975 100644 --- a/src/components/video-editor/projectPersistence.ts +++ b/src/components/video-editor/projectPersistence.ts @@ -35,7 +35,6 @@ import { DEFAULT_WEBCAM_REACT_TO_ZOOM, DEFAULT_WEBCAM_SHADOW, DEFAULT_WEBCAM_SIZE, - DEFAULT_WEBCAM_TIME_OFFSET_MS, DEFAULT_FIGURE_DATA, DEFAULT_PLAYBACK_SPEED, DEFAULT_ZOOM_DEPTH, @@ -586,9 +585,6 @@ export function normalizeProjectEditor(editor: Partial): Pro enabled: typeof webcam.enabled === "boolean" ? webcam.enabled : DEFAULT_WEBCAM_OVERLAY.enabled, sourcePath: webcamSourcePath, - timeOffsetMs: isFiniteNumber(webcam.timeOffsetMs) - ? Math.round(clamp(webcam.timeOffsetMs, -30_000, 30_000)) - : DEFAULT_WEBCAM_TIME_OFFSET_MS, mirror: typeof webcam.mirror === "boolean" ? webcam.mirror : DEFAULT_WEBCAM_OVERLAY.mirror, positionPreset: webcam.positionPreset === "top-left" || diff --git a/src/components/video-editor/types.ts b/src/components/video-editor/types.ts index 2c5eaf76..4eedb513 100644 --- a/src/components/video-editor/types.ts +++ b/src/components/video-editor/types.ts @@ -69,7 +69,6 @@ export type WebcamPositionPreset = export interface WebcamOverlaySettings { enabled: boolean; sourcePath: string | null; - timeOffsetMs: number; mirror: boolean; corner: WebcamCorner; positionPreset: WebcamPositionPreset; @@ -105,12 +104,10 @@ export const DEFAULT_WEBCAM_MARGIN = 24; export const DEFAULT_WEBCAM_POSITION_PRESET: WebcamPositionPreset = "bottom-right"; export const DEFAULT_WEBCAM_POSITION_X = 1; export const DEFAULT_WEBCAM_POSITION_Y = 1; -export const DEFAULT_WEBCAM_TIME_OFFSET_MS = 0; export const DEFAULT_WEBCAM_OVERLAY: WebcamOverlaySettings = { enabled: false, sourcePath: null, - timeOffsetMs: DEFAULT_WEBCAM_TIME_OFFSET_MS, mirror: true, corner: "bottom-right", positionPreset: DEFAULT_WEBCAM_POSITION_PRESET, diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index a47c41ab..5561b059 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -79,8 +79,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn { const chunks = useRef([]); const webcamChunks = useRef([]); const startTime = useRef(0); - const webcamStartTime = useRef(null); - const webcamTimeOffsetMs = useRef(0); const recordingSessionTimestamp = useRef(null); const nativeScreenRecording = useRef(false); const nativeWindowsRecording = useRef(false); @@ -170,10 +168,11 @@ export function useScreenRecorder(): UseScreenRecorderReturn { const selectMimeType = () => { const preferred = [ + "video/webm;codecs=av1", + "video/webm;codecs=h264", "video/webm;codecs=vp9", "video/webm;codecs=vp8", "video/webm", - "video/webm;codecs=av1", ]; return preferred.find((type) => MediaRecorder.isTypeSupported(type)) ?? "video/webm"; @@ -267,7 +266,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn { await window.electronAPI.setCurrentRecordingSession({ videoPath, webcamPath, - timeOffsetMs: webcamTimeOffsetMs.current, }); } else { await window.electronAPI.setCurrentVideoPath(videoPath); @@ -296,8 +294,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn { const startWebcamRecorder = useCallback(async () => { if (!webcamEnabled) { pendingWebcamPathPromise.current = Promise.resolve(null); - webcamStartTime.current = null; - webcamTimeOffsetMs.current = 0; return; } @@ -350,7 +346,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { return; } - const duration = Math.max(0, getRecordingDurationMs(Date.now()) - webcamTimeOffsetMs.current); + const duration = getRecordingDurationMs(Date.now()); const webcamBlob = new Blob(webcamChunks.current, { type: mimeType }); webcamChunks.current = []; const fixedBlob = await fixWebmDuration(webcamBlob, duration); @@ -363,7 +359,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn { } finally { webcamStopResolver.current = null; webcamRecorder.current = null; - webcamStartTime.current = null; if (webcamStream.current) { webcamStream.current.getTracks().forEach((track) => track.stop()); webcamStream.current = null; @@ -371,15 +366,12 @@ export function useScreenRecorder(): UseScreenRecorderReturn { } }; - webcamStartTime.current = Date.now(); recorder.start(RECORDER_TIMESLICE_MS); } catch (error) { console.warn("Failed to start webcam recording; continuing without webcam layer:", error); pendingWebcamPathPromise.current = Promise.resolve(null); webcamStopPromise.current = Promise.resolve(null); webcamRecorder.current = null; - webcamStartTime.current = null; - webcamTimeOffsetMs.current = 0; if (webcamStream.current) { webcamStream.current.getTracks().forEach((track) => track.stop()); webcamStream.current = null; @@ -410,13 +402,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { if (isNativeWindows) { const muxResult = await window.electronAPI.muxNativeWindowsRecording(); - if (!muxResult?.success || !muxResult.path) { - console.error("Failed to finalize native Windows recording:", muxResult?.error ?? muxResult?.message); - alert("Recording could not be finalized because the captured video file is invalid."); - return; - } - - finalPath = muxResult.path; + finalPath = muxResult?.path ?? result.path; } await finalizeRecordingSession(finalPath, webcamPath); @@ -533,8 +519,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn { recordingSessionTimestamp.current = Date.now(); resetRecordingClock(recordingSessionTimestamp.current); - webcamStartTime.current = null; - webcamTimeOffsetMs.current = 0; await startWebcamRecorder(); const platform = await window.electronAPI.getPlatform(); @@ -607,13 +591,9 @@ export function useScreenRecorder(): UseScreenRecorderReturn { } if (nativeResult.success) { - const mainStartedAt = Date.now(); nativeScreenRecording.current = true; nativeWindowsRecording.current = useNativeWindowsCapture; - webcamTimeOffsetMs.current = webcamStartTime.current === null - ? 0 - : webcamStartTime.current - mainStartedAt; - resetRecordingClock(mainStartedAt); + resetRecordingClock(Date.now()); setRecording(true); window.electronAPI?.setRecordingState(true); @@ -839,12 +819,8 @@ export function useScreenRecorder(): UseScreenRecorderReturn { recorder.onerror = () => { setRecording(false); }; - const mainStartedAt = Date.now(); - resetRecordingClock(mainStartedAt); - webcamTimeOffsetMs.current = webcamStartTime.current === null - ? 0 - : webcamStartTime.current - mainStartedAt; recorder.start(RECORDER_TIMESLICE_MS); + resetRecordingClock(Date.now()); setRecording(true); window.electronAPI?.setRecordingState(true); } catch (error) { @@ -928,8 +904,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn { webcamRecorder.current = null; webcamStream.current?.getTracks().forEach((t) => t.stop()); webcamStream.current = null; - webcamStartTime.current = null; - webcamTimeOffsetMs.current = 0; pendingWebcamPathPromise.current = null; if (nativeScreenRecording.current) {