SCStreamUpdateFrameContentRect X coordinate always returns 48 instead of expected 0
Environment
- Device: MacBook Pro 13-inch
- macOS: Sequoia 15.6.1
- Xcode: 16.4
- Framework: Screen Capture Kit
Issue Description
I'm experiencing an unexpected behavior with Screen Capture Kit where the SCStreamUpdateFrameContentRect
X coordinate consistently returns 48 instead of the expected 0.
Code Context
I'm using SCContentSharingPicker
to capture screen content and implementing the SCStreamOutput
protocol to receive frame data. In my stream(_:didOutputSampleBuffer:of:)
method, I'm extracting the content rect information from the sample buffer attachments:
func stream(_ stream: SCStream, didOutputSampleBuffer sampleBuffer: CMSampleBuffer, of type: SCStreamOutputType) {
switch type {
case .screen:
guard let attachmentsArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, createIfNecessary: false) as? [[SCStreamFrameInfo: Any]] else {
return
}
guard let attachments = attachmentsArray.first else { return }
if !attachments.keys.contains(.contentRect) {
return
}
print(attachments) // X coordinate always shows 48
/*
}, __C.SCStreamFrameInfo(_rawValue: SCStreamUpdateFrameContentRect): {
Height = 540;
Width = 864;
X = 48; <<-- unexpected value
Y = 0;
}]
*/
return
// ... other cases
}
}
Expected vs Actual Behavior
- Expected: X coordinate should be 0 (indicating the content starts at the left edge of the screen)
- Actual: X coordinate is consistently 48
- Visual verification: When I display the captured screen content, it appears correctly without any offset, suggesting the actual content should indeed start at X=0
Main ViewModel Class
import Foundation
import ScreenCaptureKit
import SwiftUICore
class VM: NSObject, ObservableObject, SCContentSharingPickerObserver, SCStreamDelegate, SCStreamOutput {
@State var isRecording = false
// Error handling delegate
func stream(_ stream: SCStream, didStopWithError error: Error) {
DispatchQueue.main.async {
self.isRecording = false
}
}
var picker: SCContentSharingPicker?
func createPicker() -> SCContentSharingPicker {
if let p = picker {
return p
}
let picker = SCContentSharingPicker.shared
var config = SCContentSharingPicker.shared.defaultConfiguration //SCContentSharingPickerConfiguration()
config.allowedPickerModes = .singleDisplay
config.allowsChangingSelectedContent = false
config.excludedBundleIDs.append(Bundle.main.bundleIdentifier!)
picker.add(self)
picker.isActive = true
SCContentSharingPicker.shared.present(using: .display)
return picker
}
var stream: SCStream?
let videoSampleBufferQueue = DispatchQueue(label: "com.example.apple-samplecode.VideoSampleBufferQueue")
// observer call back for picker
func contentSharingPicker(_ picker: SCContentSharingPicker, didUpdateWith filter:
SCContentFilter, for stream: SCStream?) {
if let stream = stream {
stream.updateContentFilter(filter)
} else {
let config = SCStreamConfiguration()
config.capturesAudio = false
config.captureMicrophone = false
config.captureResolution = .automatic
config.captureDynamicRange = .SDR
config.showMouseClicks = false
config.showsCursor = false
// Set the frame rate for screen capture
config.minimumFrameInterval = CMTime(value: 1, timescale: 5)
self.stream = SCStream(filter: filter, configuration: config, delegate: self)
do {
try self.stream?.addStreamOutput(self, type: .screen, sampleHandlerQueue: self.videoSampleBufferQueue)
} catch {
print("\(error)")
}
self.stream?.updateContentFilter(filter)
DispatchQueue.main.async {
self.stream?.startCapture()
}
}
}
func contentSharingPicker(_ picker: SCContentSharingPicker, didCancelFor stream: SCStream?) {}
func contentSharingPickerStartDidFailWithError(_ error: any Error) {
print(error)
}
func stream(_ stream: SCStream, didOutputSampleBuffer sampleBuffer: CMSampleBuffer, of type: SCStreamOutputType) {
switch type {
case .screen:
guard let attachmentsArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer,
createIfNecessary: false) as? [[SCStreamFrameInfo: Any]] else {
return
}
guard let attachments = attachmentsArray.first else { return }
if !attachments.keys.contains(.contentRect) {
return
}
print(attachments)
return
case .audio:
return
case .microphone:
return
@unknown default:
return
}
}
func outputVideoEffectDidStart(for stream: SCStream) {
print("outputVideoEffectDidStart")
}
func outputVideoEffectDidStop(for stream: SCStream) {
print("outputVideoEffectDidStop")
}
func streamDidBecomeActive(_ stream: SCStream) {
print("streamDidBecomeActive")
}
func streamDidBecomeInactive(_ stream: SCStream) {
print("streamDidBecomeInactive")
}
}