From 0f052b13822c1112b98b5f0ffc3b7109087cf6f0 Mon Sep 17 00:00:00 2001 From: kx Date: Tue, 16 Jun 2026 20:55:10 +0800 Subject: [PATCH] Fix mac capture duration for static recordings --- .../native/ScreenCaptureKitRecorder.swift | 58 +++++++++++++++---- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/electron/native/ScreenCaptureKitRecorder.swift b/electron/native/ScreenCaptureKitRecorder.swift index 1e2a397a..77f8869b 100644 --- a/electron/native/ScreenCaptureKitRecorder.swift +++ b/electron/native/ScreenCaptureKitRecorder.swift @@ -370,16 +370,13 @@ final class ScreenCaptureRecorder: NSObject, SCStreamOutput, SCStreamDelegate { stream = nil isRecording = false - if let originalBuffer = lastSampleBuffer, let videoInput = videoInput { - let additionalTime = lastVideoPresentationTime + frameDuration(for: originalBuffer) - let timing = CMSampleTimingInfo(duration: originalBuffer.duration, presentationTimeStamp: additionalTime, decodeTimeStamp: originalBuffer.decodeTimeStamp) - if let additionalSampleBuffer = try? CMSampleBuffer(copying: originalBuffer, withNewTiming: [timing]) { - videoInput.append(additionalSampleBuffer) - } - } - let videoEndTime = lastVideoPresentationTime + (lastSampleBuffer.map { frameDuration(for: $0) } ?? .zero) - let endTime = resolvedCaptureEndTime(videoEndTime: videoEndTime) + let captureEndTime = currentAdjustedCaptureTime() + let paddedVideoEndTime = appendFinalVideoFrameIfNeeded( + videoEndTime: videoEndTime, + captureEndTime: captureEndTime + ) + let endTime = resolvedCaptureEndTime(videoEndTime: paddedVideoEndTime) assetWriter?.endSession(atSourceTime: endTime) videoInput?.markAsFinished() inlineAudioInput?.markAsFinished() @@ -469,6 +466,48 @@ final class ScreenCaptureRecorder: NSObject, SCStreamOutput, SCStreamDelegate { return CMTime(value: 1, timescale: CMTimeScale(targetCaptureFPS)) } + private func currentAdjustedCaptureTime() -> CMTime { + guard firstSampleTime != .zero else { + return .zero + } + + let now = CMClockGetTime(CMClockGetHostTimeClock()) + let pauseDurationInProgress: CMTime + if isPaused, let pauseStartedHostTime { + pauseDurationInProgress = max(.zero, now - pauseStartedHostTime) + } else { + pauseDurationInProgress = .zero + } + + return max(.zero, now - firstSampleTime - accumulatedPausedDuration - pauseDurationInProgress) + } + + private func appendFinalVideoFrameIfNeeded(videoEndTime: CMTime, captureEndTime: CMTime) -> CMTime { + guard let originalBuffer = lastSampleBuffer, let videoInput else { + return videoEndTime + } + + let duration = frameDuration(for: originalBuffer) + let minimumAdditionalTime = videoEndTime + let targetPresentationTime = max( + minimumAdditionalTime, + captureEndTime - duration + ) + let timing = CMSampleTimingInfo( + duration: duration, + presentationTimeStamp: targetPresentationTime, + decodeTimeStamp: originalBuffer.decodeTimeStamp + ) + if let finalSampleBuffer = try? CMSampleBuffer(copying: originalBuffer, withNewTiming: [timing]), + videoInput.append(finalSampleBuffer) { + lastVideoPresentationTime = targetPresentationTime + lastVideoDuration = duration + return targetPresentationTime + duration + } + + return videoEndTime + } + private func latestInlineAudioEndTime() -> CMTime { guard lastInlineAudioPresentationTime.isValid else { return .invalid @@ -715,4 +754,3 @@ DispatchQueue.global(qos: .utility).async { } service.waitUntilFinished() -