Skip to content

Commit 85d0dea

Browse files
Merge remote-tracking branch 'origin/main' into feat/zoom-hold-preview
# Conflicts: # src/components/video-editor/VideoPlayback.tsx
2 parents 259bfa9 + b6b37e3 commit 85d0dea

18 files changed

Lines changed: 564 additions & 154 deletions

electron/electron-env.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,14 @@ interface Window {
117117
discarded?: boolean;
118118
error?: string;
119119
}>;
120+
pauseNativeWindowsRecording: () => Promise<{
121+
success: boolean;
122+
error?: string;
123+
}>;
124+
resumeNativeWindowsRecording: () => Promise<{
125+
success: boolean;
126+
error?: string;
127+
}>;
120128
startNativeMacRecording: (
121129
request: import("../src/lib/nativeMacRecording").NativeMacRecordingRequest,
122130
) => Promise<import("../src/lib/nativeMacRecording").NativeMacRecordingStartResult>;

electron/ipc/handlers.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,10 @@ let nativeWindowsCaptureWebcamTargetPath: string | null = null;
384384
let nativeWindowsCaptureRecordingId: number | null = null;
385385
let nativeWindowsCursorOffsetMs = 0;
386386
let nativeWindowsCursorCaptureMode: CursorCaptureMode = "editable-overlay";
387+
let nativeWindowsCursorRecordingStartMs = 0;
388+
let nativeWindowsPauseStartedAtMs: number | null = null;
389+
let nativeWindowsPauseRanges: Array<{ startMs: number; endMs: number }> = [];
390+
let nativeWindowsIsPaused = false;
387391
const NATIVE_WINDOWS_CAPTURE_STOP_TIMEOUT_MS = 15_000;
388392
let nativeMacCaptureProcess: ChildProcessWithoutNullStreams | null = null;
389393
let nativeMacCaptureOutput = "";
@@ -873,6 +877,18 @@ function completeNativeMacCursorPauseRange(endMs = Date.now()) {
873877
nativeMacPauseStartedAtMs = null;
874878
}
875879

880+
function completeNativeWindowsCursorPauseRange(endMs = Date.now()) {
881+
if (nativeWindowsPauseStartedAtMs === null || nativeWindowsCursorRecordingStartMs <= 0) {
882+
return;
883+
}
884+
885+
nativeWindowsPauseRanges.push({
886+
startMs: Math.max(0, nativeWindowsPauseStartedAtMs - nativeWindowsCursorRecordingStartMs),
887+
endMs: Math.max(0, endMs - nativeWindowsCursorRecordingStartMs),
888+
});
889+
nativeWindowsPauseStartedAtMs = null;
890+
}
891+
876892
function waitForNativeWindowsCaptureStart(proc: ChildProcessWithoutNullStreams) {
877893
return new Promise<void>((resolve, reject) => {
878894
const timer = setTimeout(() => {
@@ -1583,9 +1599,14 @@ export function registerIpcHandlers(
15831599
nativeWindowsCaptureRecordingId = recordingId;
15841600
nativeWindowsCursorOffsetMs = 0;
15851601
nativeWindowsCursorCaptureMode = cursorCaptureMode;
1602+
nativeWindowsCursorRecordingStartMs = 0;
1603+
nativeWindowsPauseStartedAtMs = null;
1604+
nativeWindowsPauseRanges = [];
1605+
nativeWindowsIsPaused = false;
15861606

15871607
const cursorStartTimeMs = Date.now();
15881608
if (cursorCaptureMode === "editable-overlay") {
1609+
nativeWindowsCursorRecordingStartMs = cursorStartTimeMs;
15891610
await startCursorRecording(cursorStartTimeMs);
15901611
console.info("[native-wgc] cursor sampler ready", {
15911612
cursorStartTimeMs,
@@ -1635,6 +1656,10 @@ export function registerIpcHandlers(
16351656
nativeWindowsCaptureRecordingId = null;
16361657
nativeWindowsCursorOffsetMs = 0;
16371658
nativeWindowsCursorCaptureMode = "editable-overlay";
1659+
nativeWindowsCursorRecordingStartMs = 0;
1660+
nativeWindowsPauseStartedAtMs = null;
1661+
nativeWindowsPauseRanges = [];
1662+
nativeWindowsIsPaused = false;
16381663
await stopCursorRecording();
16391664
return { success: false, error: String(error) };
16401665
}
@@ -1836,6 +1861,50 @@ export function registerIpcHandlers(
18361861
}
18371862
});
18381863

1864+
ipcMain.handle("pause-native-windows-recording", async () => {
1865+
const proc = nativeWindowsCaptureProcess;
1866+
if (!proc) {
1867+
return { success: false, error: "Native Windows capture is not running." };
1868+
}
1869+
if (nativeWindowsIsPaused) {
1870+
return { success: true };
1871+
}
1872+
if (!proc.stdin.writable) {
1873+
return { success: false, error: "Native Windows capture command channel is closed." };
1874+
}
1875+
1876+
try {
1877+
proc.stdin.write("pause\n");
1878+
nativeWindowsIsPaused = true;
1879+
nativeWindowsPauseStartedAtMs = Date.now();
1880+
return { success: true };
1881+
} catch (error) {
1882+
return { success: false, error: error instanceof Error ? error.message : String(error) };
1883+
}
1884+
});
1885+
1886+
ipcMain.handle("resume-native-windows-recording", async () => {
1887+
const proc = nativeWindowsCaptureProcess;
1888+
if (!proc) {
1889+
return { success: false, error: "Native Windows capture is not running." };
1890+
}
1891+
if (!nativeWindowsIsPaused) {
1892+
return { success: true };
1893+
}
1894+
if (!proc.stdin.writable) {
1895+
return { success: false, error: "Native Windows capture command channel is closed." };
1896+
}
1897+
1898+
try {
1899+
proc.stdin.write("resume\n");
1900+
completeNativeWindowsCursorPauseRange();
1901+
nativeWindowsIsPaused = false;
1902+
return { success: true };
1903+
} catch (error) {
1904+
return { success: false, error: error instanceof Error ? error.message : String(error) };
1905+
}
1906+
});
1907+
18391908
ipcMain.handle("stop-native-windows-recording", async (_, discard?: boolean) => {
18401909
const proc = nativeWindowsCaptureProcess;
18411910
const preferredPath = nativeWindowsCaptureTargetPath;
@@ -1848,6 +1917,7 @@ export function registerIpcHandlers(
18481917
}
18491918

18501919
try {
1920+
completeNativeWindowsCursorPauseRange();
18511921
const stoppedPathPromise = waitForNativeWindowsCaptureStop(proc);
18521922
proc.stdin.write("stop\n");
18531923
const stoppedPath = await stoppedPathPromise;
@@ -1872,6 +1942,7 @@ export function registerIpcHandlers(
18721942
}
18731943

18741944
if (cursorCaptureMode === "editable-overlay") {
1945+
compactPendingCursorTelemetryPauseRanges(nativeWindowsPauseRanges);
18751946
shiftPendingCursorTelemetry(nativeWindowsCursorOffsetMs);
18761947
await writePendingCursorTelemetry(screenVideoPath);
18771948
}
@@ -1913,6 +1984,10 @@ export function registerIpcHandlers(
19131984
nativeWindowsCaptureRecordingId = null;
19141985
nativeWindowsCursorOffsetMs = 0;
19151986
nativeWindowsCursorCaptureMode = "editable-overlay";
1987+
nativeWindowsCursorRecordingStartMs = 0;
1988+
nativeWindowsPauseStartedAtMs = null;
1989+
nativeWindowsPauseRanges = [];
1990+
nativeWindowsIsPaused = false;
19161991
const source = selectedSource || { name: "Screen" };
19171992
if (onRecordingStateChange) {
19181993
onRecordingStateChange(false, source.name);

electron/native/wgc-capture/src/audio_sample_utils.cpp

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ bool AudioMixer::start() {
279279
stopRequested_ = false;
280280
emittedFrames_ = 0;
281281
timelineStarted_ = false;
282+
paused_ = false;
282283
thread_ = std::thread([this] {
283284
mixLoop();
284285
});
@@ -296,6 +297,18 @@ void AudioMixer::beginTimeline() {
296297
cv_.notify_all();
297298
}
298299

300+
void AudioMixer::setPaused(bool paused) {
301+
{
302+
std::scoped_lock lock(mutex_);
303+
paused_ = paused;
304+
if (paused_) {
305+
systemQueue_.clear();
306+
microphoneQueue_.clear();
307+
}
308+
}
309+
cv_.notify_all();
310+
}
311+
299312
void AudioMixer::stop() {
300313
stopRequested_ = true;
301314
cv_.notify_all();
@@ -311,6 +324,9 @@ void AudioMixer::pushSystem(const BYTE* data, DWORD byteCount) {
311324

312325
{
313326
std::scoped_lock lock(mutex_);
327+
if (paused_) {
328+
return;
329+
}
314330
append(systemQueue_, data, byteCount, systemFormat_, 1.0);
315331
}
316332
cv_.notify_all();
@@ -323,6 +339,9 @@ void AudioMixer::pushMicrophone(const BYTE* data, DWORD byteCount) {
323339

324340
{
325341
std::scoped_lock lock(mutex_);
342+
if (paused_) {
343+
return;
344+
}
326345
append(microphoneQueue_, data, byteCount, microphoneFormat_, microphoneGain_);
327346
}
328347
cv_.notify_all();
@@ -371,13 +390,13 @@ void AudioMixer::mixLoop() {
371390
const bool hasMicrophone = !includeMicrophone_ || microphoneQueue_.size() >= chunkBytes;
372391
const bool hasAnySource = !systemQueue_.empty() || !microphoneQueue_.empty();
373392
return stopRequested_.load() ||
374-
(timelineStarted_ && (hasSystem || hasMicrophone) && hasAnySource);
393+
(timelineStarted_ && !paused_ && (hasSystem || hasMicrophone) && hasAnySource);
375394
});
376395

377396
if (stopRequested_) {
378397
break;
379398
}
380-
if (!timelineStarted_) {
399+
if (!timelineStarted_ || paused_) {
381400
continue;
382401
}
383402

electron/native/wgc-capture/src/audio_sample_utils.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class AudioMixer {
5252

5353
bool start();
5454
void beginTimeline();
55+
void setPaused(bool paused);
5556
void stop();
5657
void pushSystem(const BYTE* data, DWORD byteCount);
5758
void pushMicrophone(const BYTE* data, DWORD byteCount);
@@ -81,5 +82,6 @@ class AudioMixer {
8182
std::thread thread_;
8283
std::atomic<bool> stopRequested_ = false;
8384
bool timelineStarted_ = false;
85+
bool paused_ = false;
8486
uint64_t emittedFrames_ = 0;
8587
};

0 commit comments

Comments
 (0)