mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-26 15:55:35 +00:00
Keep window crops aligned as windows change
This commit is contained in:
@@ -40,6 +40,8 @@ final class ScreenCaptureRecorder: NSObject, SCStreamOutput, SCStreamDelegate {
|
||||
private var videoInput: AVAssetWriterInput?
|
||||
private var videoPixelBufferAdaptor: AVAssetWriterInputPixelBufferAdaptor?
|
||||
private var windowCropRect: CGRect?
|
||||
private var windowCropDisplayId: CGDirectDisplayID?
|
||||
private var excludedProcessIds = Set<Int32>()
|
||||
private var lastCroppedPixelBuffer: CVPixelBuffer?
|
||||
private let imageContext = CIContext(options: [.cacheIntermediates: false])
|
||||
private var systemAudioWriter: AVAssetWriter?
|
||||
@@ -123,7 +125,7 @@ final class ScreenCaptureRecorder: NSObject, SCStreamOutput, SCStreamDelegate {
|
||||
let filter: SCContentFilter
|
||||
let outputWidth: Int
|
||||
let outputHeight: Int
|
||||
let excludedProcessIds = Set(config.excludedProcessIds ?? [])
|
||||
excludedProcessIds = Set(config.excludedProcessIds ?? [])
|
||||
let excludedApplications = availableContent.applications.filter {
|
||||
excludedProcessIds.contains($0.processID)
|
||||
}
|
||||
@@ -134,14 +136,7 @@ final class ScreenCaptureRecorder: NSObject, SCStreamOutput, SCStreamDelegate {
|
||||
throw NSError(domain: "RecordlyCapture", code: 3, userInfo: [NSLocalizedDescriptionKey: "Window not found"])
|
||||
}
|
||||
|
||||
guard let display = availableContent.displays.first(where: {
|
||||
$0.frame.intersects(window.frame) || $0.frame.contains(CGPoint(x: window.frame.midX, y: window.frame.midY))
|
||||
}) else {
|
||||
throw NSError(domain: "RecordlyCapture", code: 4, userInfo: [NSLocalizedDescriptionKey: "Window display not found"])
|
||||
}
|
||||
|
||||
// Accessibility reports the visible frame at the native border.
|
||||
let scaleFactor = ScreenCaptureRecorder.scaleFactor(for: display.displayID)
|
||||
let visibleFrame: CGRect
|
||||
if let x = config.windowX,
|
||||
let y = config.windowY,
|
||||
@@ -153,6 +148,10 @@ final class ScreenCaptureRecorder: NSObject, SCStreamOutput, SCStreamDelegate {
|
||||
} else {
|
||||
visibleFrame = window.frame
|
||||
}
|
||||
guard let display = Self.captureDisplay(for: visibleFrame, from: availableContent.displays) else {
|
||||
throw NSError(domain: "RecordlyCapture", code: 4, userInfo: [NSLocalizedDescriptionKey: "Window display not found"])
|
||||
}
|
||||
let scaleFactor = ScreenCaptureRecorder.scaleFactor(for: display.displayID)
|
||||
let captureRect = visibleFrame.intersection(display.frame)
|
||||
filter = SCContentFilter(
|
||||
display: display,
|
||||
@@ -165,6 +164,7 @@ final class ScreenCaptureRecorder: NSObject, SCStreamOutput, SCStreamDelegate {
|
||||
width: captureRect.width / display.frame.width,
|
||||
height: captureRect.height / display.frame.height
|
||||
)
|
||||
windowCropDisplayId = display.displayID
|
||||
outputWidth = max(2, Int(captureRect.width) * scaleFactor) & ~1
|
||||
outputHeight = max(2, Int(captureRect.height) * scaleFactor) & ~1
|
||||
streamConfig.width = max(2, Int(display.frame.width) * scaleFactor)
|
||||
@@ -173,6 +173,7 @@ final class ScreenCaptureRecorder: NSObject, SCStreamOutput, SCStreamDelegate {
|
||||
} else {
|
||||
trackedWindowId = nil
|
||||
windowCropRect = nil
|
||||
windowCropDisplayId = nil
|
||||
let displayId = config.displayId ?? CGMainDisplayID()
|
||||
guard let display = availableContent.displays.first(where: { $0.displayID == displayId }) else {
|
||||
throw NSError(domain: "RecordlyCapture", code: 4, userInfo: [NSLocalizedDescriptionKey: "Display not found"])
|
||||
@@ -637,6 +638,8 @@ final class ScreenCaptureRecorder: NSObject, SCStreamOutput, SCStreamDelegate {
|
||||
videoInput = nil
|
||||
videoPixelBufferAdaptor = nil
|
||||
windowCropRect = nil
|
||||
windowCropDisplayId = nil
|
||||
excludedProcessIds.removeAll()
|
||||
lastCroppedPixelBuffer = nil
|
||||
systemAudioWriter = nil
|
||||
systemAudioInput = nil
|
||||
@@ -869,8 +872,7 @@ final class ScreenCaptureRecorder: NSObject, SCStreamOutput, SCStreamDelegate {
|
||||
continue
|
||||
}
|
||||
|
||||
let windowStillAvailable = availableContent.windows.contains(where: { $0.windowID == trackedWindowId })
|
||||
if !windowStillAvailable {
|
||||
guard let window = availableContent.windows.first(where: { $0.windowID == trackedWindowId }) else {
|
||||
print("WINDOW_UNAVAILABLE")
|
||||
fflush(stdout)
|
||||
let finalization = await self.finalizeCapture(interactive: false)
|
||||
@@ -887,11 +889,59 @@ final class ScreenCaptureRecorder: NSObject, SCStreamOutput, SCStreamDelegate {
|
||||
fflush(stderr)
|
||||
exit(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
guard let display = Self.captureDisplay(for: window.frame, from: availableContent.displays) else {
|
||||
continue
|
||||
}
|
||||
let captureRect = window.frame.intersection(display.frame)
|
||||
guard captureRect.width > 0, captureRect.height > 0 else { continue }
|
||||
let cropRect = CGRect(
|
||||
x: (captureRect.minX - display.frame.minX) / display.frame.width,
|
||||
y: (captureRect.minY - display.frame.minY) / display.frame.height,
|
||||
width: captureRect.width / display.frame.width,
|
||||
height: captureRect.height / display.frame.height
|
||||
)
|
||||
|
||||
if self.windowCropDisplayId != display.displayID, let activeStream = self.stream {
|
||||
let excludedApplications = availableContent.applications.filter {
|
||||
self.excludedProcessIds.contains($0.processID)
|
||||
}
|
||||
let filter = SCContentFilter(
|
||||
display: display,
|
||||
excludingApplications: excludedApplications,
|
||||
exceptingWindows: []
|
||||
)
|
||||
do {
|
||||
try await activeStream.updateContentFilter(filter)
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
|
||||
self.queue.async {
|
||||
if self.isRecording {
|
||||
self.windowCropRect = cropRect
|
||||
self.windowCropDisplayId = display.displayID
|
||||
}
|
||||
continuation.resume()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func captureDisplay(for frame: CGRect, from displays: [SCDisplay]) -> SCDisplay? {
|
||||
let midpoint = CGPoint(x: frame.midX, y: frame.midY)
|
||||
return displays.first(where: { $0.frame.contains(midpoint) })
|
||||
?? displays.filter { $0.frame.intersects(frame) }.max {
|
||||
$0.frame.intersection(frame).width * $0.frame.intersection(frame).height
|
||||
< $1.frame.intersection(frame).width * $1.frame.intersection(frame).height
|
||||
}
|
||||
}
|
||||
|
||||
private static func scaleFactor(for displayId: CGDirectDisplayID) -> Int {
|
||||
guard let mode = CGDisplayCopyDisplayMode(displayId) else {
|
||||
return 1
|
||||
|
||||
Reference in New Issue
Block a user