ScreenCaptureKit

RSS for tag

ScreenCaptureKit brings high-performance screen capture, including audio and video, to macOS.

Posts under ScreenCaptureKit tag

130 Posts

Post

Replies

Boosts

Views

Activity

SCStreamOutputType.audio delivers correctly-formed but all-zero PCM for VoIP background audio
Is silently zeroing .audio output for communications/VoIP-category background app audio (while leaving frame delivery, timing, and format metadata intact) expected, documented behavior on iOS? Is there an SCStreamConfiguration or SCContentFilter setting that unlocks this, or is this a known gap in the current iOS ScreenCaptureKit implementation relative to macOS (where .audio is documented/known to capture other apps' audio including calls)? A pointer to relevant documentation or a radar number would be very helpful. My test app: Rabbler Rabbler main use it turning voice to text transcriptions. ie. meeting notes. It works well using the iPhone mic or AirPods. It fails when Zoom or Teams meeting is joined. Audio is paused by a well handled interruption. But that yields lost transcription text. Environment iOS 27.0, physical device iPhone 15ProMax (not simulator) Xcode 27.0 (Build 27A266a), iPhoneOS27.0 SDK App target UIBackgroundModes: audio, screen-capture Capture another app's audio via SCStream's .audio output type, using SCContentSharingPicker for source selection, per the "Capturing screen content on iOS" sample's picker pattern. Setup (trimmed to the relevant parts) var configuration = SCContentSharingPickerConfiguration() configuration.showsMicrophoneControl = true picker.defaultConfiguration = configuration picker.add(self) picker.isActive = true picker.present() // contentSharingPicker(_:didUpdateWith:for:) -> startStream(with:) let config = SCStreamConfiguration() config.capturesAudio = true let newStream = SCStream(filter: filter, configuration: config, delegate: self) try newStream.addStreamOutput(seldlerQueue: .main) if filter.isMicrophoneEnabled { try newStream.addStreamOutputsampleHandlerQueue: .main) } try await newStream.startCapture( Sample-buffer handling, convertinmeasuring amplitude: func stream(_ stream: SCStream, deBuffer: CMSampleBuffer, of type:SCStreamOutputType) { guard sampleBuffer.isValid, tmicrophone else { return } // ... (format captured once from first buffer via CMAudioFormatDescriptionGetStream let frameCount = AVAudioFrameCount(CMSampleBufferGetNumSamples(sampleBuffer)) let pcmBuffer = AVAudioPCMBufmeCapacity: frameCount)! pcmBuffer.frameLength = frameCount let status = CMSampleBufferCost( sampleBuffer, at: 0, frameCount: Int32(frameCount), into: pcmBuffer.mutableAudioBufferList ) // status == noErr every time } Amplitude check on the resulting AVAudioPCMBuffer.floatChannelData: var peak: Float = 0, sumSquares: 0 for channel in 0..<Int(buffer.format.channelCount) { let samples = channelData[cha for frame in 0..<frameCount { let sample = samples[fram peak = max(peak, abs(sample)) sumSquares += sample * sa if sample != 0 { nonZeroCount += 1 } } } Result 1 — Music app selected as capture source (baseline, works correctly) Real audio content: writing the buffers straight to a .caf file via AVAudioFile produces a real, listenable ~9MB file matchihe capture duration (stereo, 48kHz, Float32). Confirmed by ear. Result 2 — Zoom call selected as capture source (fails silently) SCStream delivers buffers continuously and correctly-formed — same format every time (2 ch, 48000 Hz, Float32, deinterleaved)atus == noErr fromCMSampleBufferCopyPCMDataIntoAudioBufferList every time. But every sample is exactly zero: [2:17:19 PM] ScreenCaptureService: .audio first buffer format — <AVAudioFormat 0x12063fb60: 2ch, 48000 Hz, Float32, deinterlea [2:17:19 PM] ScreenCaptureService: .audio buffer #1 — peak=0.0 rms=0.0 nonZero=0/1920 [2:17:19 PM] ScreenCaptureService0.0 rms=0.0 nonZero=0/1920 ... [2:17:21 PM] ScreenCaptureServicek=0.0 rms=0.0 nonZero=0/1920 [2:18:27 PM] ScreenCaptureService: .audio buffer #3400 — peak=0.0 rms=0.0 nonZero=0/1920 That's peak=0.0/rms=0.0/0 nonZero samples across every single one of 3400+ consecutive buffersover ~70 seconds of an active Zoo Writing these buffers to a .caffile produces a valid, correctly-sized, completely silent audio file — not corrupted, not empty, genuinely all zeros. Control test — .microphone in theall With filter.isMicrophoneEnabled = on the same SCStream, during thesame Zoom call, correctly captures the local user's own voice (confirmed by ear from the resulting file) — even though the call is silently holding exclusive access to the mic hardware from Rabbler's own AVAudioEngine.inputNode tap (which gets interrupted, as expected). This rules out a session-wide permission failure -.micophone clearly has real access to audio in this exact session; .audio does not, specifically for this source. evidence now looks like this iOS 27 ScreenCaptureKit │ ├── SomaFM playback │ └── .audio → REAL PCM ✓ │ ├── Zoom remote audio │ └── .audio → ZERO PCM ✗ │ └── Teams remote audio └── .audio → ZERO PCM ✗
2
0
529
1d
ScreenCaptureKit authorization fails after tccd exhausts file descriptors (FB24757092)
I’m seeking DTS guidance on supported ScreenCaptureKit usage and diagnostics for an intermittent authorization failure, filed as FB24757092. Captured evidence (my incident) On macOS 26.6.2 (25G83), Apple silicon, an already-authorized custom Apple Development-signed AltTabDebug build encountered renewed Screen Recording prompts. Unified logs show root tccd exhausting file descriptors during authorization. Three failing tccd processes each reported 255 total descriptors, with 250 unique descriptors referring to the same app executable. SecStaticCodeCreateWithPath failed with error 100024 (UNIX[Too many open files]); tccd could not match the existing kTCCServiceScreenCapture code requirement, and replayd reported TCC Disallow / user denied for captureScreenshot. Later checks allowed the same running app again. This confirms resource exhaustion and authorization failures. It does not establish why descriptors accumulated, a persistent leak mechanism, a per-capture leak rate, or a deterministic reproducer. The reported load spike and roughly one-second display freeze have an uncertain causal relationship to these failures. I have not established reproduction on macOS 27 or descriptor exhaustion in an unmodified release build. No incident-time sysdiagnose was collected. Separate upstream observations The AltTab maintainer reported testing the signed release in /Applications on the same OS build: 446 screenshots produced 1,350 ScreenCapture authorization requests, with repeated code-signature validation; approximately 23 ms of root-tccd CPU per thumbnail was reported. These are the maintainer’s measurements, not independently repeated by me, and do not measure descriptor growth per capture. https://github.com/lwouis/alt-tab-macos/issues/6025 https://github.com/lwouis/alt-tab-macos/issues/6025#issuecomment-5640382131 Questions Is there a supported ScreenCaptureKit capture pattern, request concurrency/rate guidance, or recovery strategy to reduce repeated authorization work and avoid renewed prompts when code validation fails from transient resource exhaustion? How should an app distinguish a genuine permission denial from an unavailable/failed authorization service, without repeatedly requesting permission? Which logging profile, trace, or incident-time diagnostics should we collect to correlate capture submissions/completions with tccd descriptor lifetime and isolate the accumulation mechanism? FB24757092 contains the focused timeline, descriptor dumps, prompt screenshot, and separately attributed maintainer findings. Raw diagnostics and private system/account information are intentionally omitted here. I built a standalone Swift/AppKit probe that uses one retained SCContentFilter per batch, logs submissions and actual callbacks, bounds outstanding requests, stops new submissions on the first error, and discards images. On macOS 27.0 (26A428), its ad-hoc-signed build captured its own window successfully in 446-request captureScreenshot batches at concurrency 1 and 4. This validates the harness, not reproduction of the original failure. The code-level form requests a focused project demonstrating the issue; this probe does not yet reproduce exhaustion. Could DTS advise the next isolation step and whether a private support case is appropriate for the existing evidence?
1
0
311
1d
ScreenCaptureKit on iPadOS 27 is capped at 60 fps on 120Hz ProMotion devices, even with minimumFrameInterval set to 1/120
On an iPad Pro 11-inch (M4) running iPadOS 27.0 (24A437), ScreenCaptureKit delivers a maximum of 60 frames per second when capturing the entire screen, even while an app is rendering at 120 fps (confirmed with the Metal Performance HUD). What I tested: Default configuration: exactly 60 fps, with every frame timestamp spaced 16.67ms apart. Setting minimumFrameInterval to 1/120 and queueDepth to 8, both before starting the stream and through updateConfiguration after it started: the values are accepted and read back correctly, but delivery stays at exactly 60 fps. Smaller output sizes (1/4 and 1/8 of native resolution): still 60 fps. ReplayKit broadcast upload extension: also exactly 60 fps. Also, minimumFrameInterval and queueDepth are documented as available on iOS/iPadOS 27, but the iOS 27 SDK marks them as unavailable. Request: please allow ScreenCaptureKit to capture at the display's full refresh rate (up to 120 fps) on ProMotion devices when minimumFrameInterval asks for it, and make minimumFrameInterval available in the iOS SDK.
0
0
231
1d
Get Desktop background image
In a WWDC 2019 "Advances in macOS Security" at 18:40 there is the following code func getDesktopWindowIds() -> [CGWindowID] { let windows = CGWindowListCopyWindowInfo([.optionOnScreenOnly], kCGNullWindowID)! as! [[String: AnyObject]] let DesktopWindowLevel = CGWindowLevelForKey(.desktopWindow)-1 let DesktopWindows = windows.filter { let windowLevel = $0[kCGWindowLayer as String] as! CGWindowLevel return windowLevel == desktopWindowLevel } return desktopWindows.map { $0[kCGWindowNumber as String] as! CGWindowID } } to find the CGWindowID of the Desktop background. This works, but when you then try to get the CGImage for that CGWindowID with let cgImage = CGWindowListCreateImage(CGRectNull, [.optionIncludingWindow], cgWin, [.bestResolution]) cgImage does get a reference, however it's just a gray image. Not the Desktop picture. It's clear from the documentation that ScreenCaptureKit should be used. However, if used I get multiple warnings to the user, the most concerning one states: "App" would like to record this computer's screen and audio. This is not true! I do nothing with audio and to say a capture is a recording is also misleading. Is there way to achieve what use to work before macOS 27? Or a way to change/avoid this misleading warning? Is there a reason why this warning is needed for capturing the Desktop background where before it was explicitly allowed?
0
0
418
3d
iOS 27: SCStreamConfiguration.excludesCurrentProcessAudio has no effect (0.0 dB separation) - what is the supported way to exclude our own audio?
On iOS, SCStreamConfiguration.excludesCurrentProcessAudio appears to do nothing. Audio produced by our own process is captured at full level, so there is currently no way to capture device audio while excluding what our app is playing. Measurement (deterministic probe, 2026-08-08): Our app plays a 1 kHz tone at -4.9 dBFS RMS from its own process while capturing device audio with SCStreamConfiguration.capturesAudio = true and excludesCurrentProcessAudio = true. The configuration in force is read back from the stream and logged, so we know the flag is actually set. The captured audio contained our own tone at -4.9 dBFS peak. Separation = 0.0 dB. 1,573 audio sample buffers analyzed over 31.5 s (16 kHz mono, 50 buffers/s), zero analysis failures, and the run was reproduced twice about 3 hours apart with byte-identical verdicts. Environment for that run: iPhone 16 Pro (iPhone17,1), iOS 27.0, built with Xcode 27 beta 4 (27A5228h) against the iOS 27 SDK, installed directly from Xcode. Still reproducing on the current build: on iOS 27.0 (24A5418b) our shipping capture path still receives our own playback. In production we now have to cancel it ourselves - we align our playback buffer to the captured stream by cross-correlation and subtract it. The correlation between the captured signal and our own playback sits at |r| = 0.65-0.85 in the affected segments, i.e. the capture is dominated by a copy of our own output, exactly what excludesCurrentProcessAudio is supposed to remove. Doing this subtraction in-process costs real CPU and only works while we can hold a delay lock. Why this is blocking: RPSampleBufferType.audioApp is deprecated as of iOS 27 and the documentation points to ScreenCaptureKit as the replacement. With excludesCurrentProcessAudio non-functional there is no supported path on iOS to capture device audio while excluding one's own process. Our app is a real-time dubbing app - it captures foreign-language audio, transcribes it, and plays back a translated voice - so our own output re-entering the capture is fed straight back into transcription and corrupts the session. My questions: (1) Is excludesCurrentProcessAudio expected to be functional on iOS 27, or is it macOS-only in practice? The documentation does not mark it as unavailable on iOS. (2) If it is expected to work, is there anything the app must do besides setting it on the SCStreamConfiguration used to start the stream? (3) If it is not going to work on iOS, what is the supported way to exclude the current process's audio from a ScreenCaptureKit capture, now that RPSampleBufferType.audioApp is deprecated? Filed as FB24170972 on 2026-08-08, with the full JSON event logs from both probe runs attached. There has been no response on the Feedback, which is why I am raising it here.
5
1
516
1w
Is there a supported way to read on-screen text with the user's consent? (worker-safety / income-transparency app)
Hello, I am an independent developer in Argentina building an app for rideshare drivers, and I would like to know what the supported approach is on iOS before I build the wrong thing. The problem When a trip offer appears, the driver sees a fare, a distance and a duration. What they do not see is what is left after the cost of running their own car — fuel, maintenance, and very often the weekly rent they pay for the vehicle. Here those costs are high and change constantly, and drivers routinely accept trips that lose them money without realising it. They have about three seconds to decide, while driving. Our app reads the numbers already visible on the driver's own screen, subtracts that particular driver's cost per kilometre, and shows one figure: what this trip actually leaves them per hour. In our own field measurements with two drivers over several weeks, being able to see that number raised their hourly earnings and — just as important — kept their eyes off mental arithmetic at the wheel. What we will not do, on purpose I want to be explicit about this, because I know other tools in this category cut corners: We never ask for the driver's Uber or DiDi username or password, and we never connect to or automate their account. Some tools do, and that puts the driver's account — their livelihood — at risk of deactivation. We will not build that. We never accept or decline a trip for the driver. The decision and the tap are always theirs. We never store passenger data: no name, no rating, no photo, no exact address. Text recognition runs entirely on device, offline. The captured image is discarded immediately and never leaves the phone or reaches any server. No advertising, no data sales, no third-party trackers. The app does not read anything the driver is not already looking at, it only reads while the driver has explicitly turned it on for their shift, and it exists for one purpose: so that someone working long hours knows what an hour of their work is actually worth. My question On Android this is done with MediaProjection, with explicit user consent each session and a persistent system indicator. On iOS I understand a Broadcast Upload Extension is not intended for background processing, and that there is no overlay across apps. Is there any supported path on iOS for an app to read, with the user's explicit and repeated consent, text the user is already looking at on their own screen, in order to give that same user an immediate assessment of their own work? Accessibility APIs, a system extension point, anything. If there is no supported path, I would rather know now and design the iOS version around what is allowed, instead of shipping something that gets rejected or that quietly breaks the rules. Thank you.
1
0
380
3w
iOS 27: ScreenCaptureKit requires UIBackgroundModes 'screen-capture', but App StoiOS 27: ScreenCaptureKit requires UIBackgroundModes 'screen-capture', but App Store Connect rejects that valuere Connect rejects that value
On iOS, ScreenCaptureKit terminates an SCStream when the app is backgrounded unless the app declares UIBackgroundModes: screen-capture. The delegate reports SCStreamError code -3824 (SCStreamError.Code.missingBackgroundMode). Adding screen-capture to UIBackgroundModes - the value Apple's own iOS 27 ScreenCaptureKit sample code declares - makes App Store Connect reject the upload: error: exportArchive Invalid Info.plist value. The Info.plist key UIBackgroundModes contains an invalid value: 'screen-capture'. So the app cannot be distributed at all, not even to TestFlight internal testers. We have ruled out simply keeping the process alive: running a continuous silent AVAudioEngine under the audio background mode keeps the app running but does not prevent -3824, which is consistent with the framework performing an explicit background-mode check rather than the stream dying from process suspension. One observation that may explain why this has gone unnoticed: Apple's sample is installed directly from Xcode and therefore never passes through App Store Connect's upload validation, so a mismatch between the sample's Info.plist and App Store Connect's allowlist would not be visible when testing the sample internally. My question is which of these three describes the actual status of screen-capture for third-party iOS apps: (1) App Store Connect's allowlist has not yet been updated for iOS 27, and this will resolve on its own. (2) screen-capture requires an entitlement or a specially provisioned profile we have not requested. If so, which one, and how is it requested? (3) screen-capture is restricted to Apple's own or system applications and is not available to third-party App Store apps by design. I could not find screen-capture documented on the general UIBackgroundModes page, which is why I cannot tell these apart. Filed as FB24169650. Device: iPhone 16 Pro, iOS 27.0 Built against the iOS 27 SDK, deployment target 17.0.
4
0
575
Aug ’26
Public API to silently query "Remote Desktop" TCC authorization status (without triggering a system prompt)
Product area macOS / Privacy & Security / ScreenCaptureKit / Core Graphics Environment macOS 27 Beta 4 (build: fill in your exact build number, e.g. 27A5xxx) Xcode 26.5 / SDK 260500 (adjust to match what you actually built with) App holds the com.apple.developer.persistent-content-capture entitlement (approved via Apple's request form), targeting macOS 14.4+ Summary Our app is a remote-support/remote-control tool (screen viewing + control), comparable to VNC-style products. On macOS 27, we've found that System Settings > Privacy & Security now shows a "Remote Desktop" entry that is distinct from "Screen & System Audio Recording" — granting one does not affect the other. We need a way to check, at any time, whether our app currently has "Remote Desktop" authorization, without causing the system to show a permission-request alert as a side effect. We have not found a documented, public API that does this. What we've tried CGPreflightScreenCaptureAccess() Confirmed via a controlled test on-device: granting only "Remote Desktop" leaves this API returning false; granting only "Screen & System Audio Recording" makes it return true. So this API appears to reflect kTCCServiceScreenCapture only, and does not reflect the "Remote Desktop" permission at all. ScreenCaptureKit (SCShareableContent, e.g. via a refreshAvailableContentWithCompletionHandler:-style call) This call does appear to interact with the "Remote Desktop" permission — but calling it triggers a real system consent alert every time we call it, even when we only intend to read the current status, not request it. This makes it unusable for passive/background status polling (e.g. to decide what to show in our own onboarding UI without surprising the user with an OS-level prompt). We are intentionally not reading /Library/Application Support/com.apple.TCC/TCC.db directly — we understand this is a private, undocumented database and want a supported API instead. Sample code illustrating both attempts // Attempt 1: CGPreflightScreenCaptureAccess — does not reflect Remote Desktop grant BOOL preflightResult = CGPreflightScreenCaptureAccess(); // preflightResult stays NO even after the user grants "Remote Desktop" in // System Settings > Privacy & Security > Remote Desktop. // It correctly flips to YES only when "Screen & System Audio Recording" is granted. // Attempt 2: ScreenCaptureKit-based check — reflects it, but prompts every time SCShareableContent... // (via our wrapper) refreshAvailableContentWithCompletionHandler: // This call appears to influence/query the Remote Desktop TCC entry, but the OS // shows a permission alert as a side effect of the call itself, even when we only // want to read the current authorization state. Question Is there a public, documented API equivalent to CGPreflightScreenCaptureAccess() — i.e., a read-only, non-prompting status check — for the new "Remote Desktop" privacy category introduced around macOS 26/27? Is com.apple.developer.persistent-content-capture actually the entitlement that governs this new "Remote Desktop" category, or is it unrelated? Apple's own documentation describes this entitlement purely in terms of "persistent access to screen capture" for VNC apps, with no mention of a distinct "Remote Desktop" permission surface — we'd like to confirm whether that description is still accurate on macOS 26/27, or whether the underlying TCC service (kTCCServiceRemoteDesktop, which we found via TCC.db schema inspection only, not public docs) has been intentionally split out. If no such API exists yet, is this planned, and is there a recommended interim approach for apps that need to know this state before deciding whether to show their own onboarding/permission UI?
2
0
580
Aug ’26
ScreenCaptureKit stops capturing after ~10–15 minutes unexpectedly
When using the built-in macOS screen recording feature, the recording stops automatically after approximately 10–15 minutes without any warning or error message. No manual stop action is performed. The recording simply ends silently. The same issue also occurs when using ScreenCaptureKit in a custom application, which suggests this may be a system-level issue related to screen capture rather than an app-specific problem. This issue is reproducible and happens consistently after running for a period of time.
0
0
534
Apr ’26
ScreenCaptureKit stops capturing after ~10–15 minutes unexpectedly
When using the built-in macOS screen recording feature, the recording stops automatically after approximately 10–15 minutes without any warning or error message. No manual stop action is performed. The recording simply ends silently. The same issue also occurs when using ScreenCaptureKit in a custom application, which suggests this may be a system-level issue related to screen capture rather than an app-specific problem. This issue is reproducible and happens consistently after running for a period of time.
0
0
553
Apr ’26
Technical guidance request: native screen capture protection on macOS with Flutter while allowing AirPlay
Hello Apple Developer Support, I am reaching out for technical guidance regarding screen capture protection behavior on macOS. We are building a desktop application using Flutter running on macOS, and we have implemented native Swift code inside the macOS Runner in order to protect sensitive content from screen recording and screen sharing. Our current implementation relies on native window-level protection and display state handling from Swift, while the main UI remains rendered by Flutter. The main challenge we are facing is the following: we need to keep a strong native anti-recording protection on macOS the application is heavily used with AirPlay and screen mirroring currently, AirPlay / mirroring is often interpreted by the system similarly to screen capture or screen recording this causes our protected content to be replaced by a gray or blank area even during legitimate AirPlay usage In practice, we would like to allow: AirPlay legitimate external display / mirroring usage while still preventing: screen recording screen sharing unauthorized screen capture We would like to know whether Apple recommends an official supported approach for this use case, preferably using public APIs. More specifically: Is there an officially supported way on macOS to distinguish AirPlay mirroring from screen recording / screen sharing? Is "NSWindow.sharingType" the recommended public API for this scenario? Is there a recommended approach when the UI surface is rendered through Flutter / Metal? Are there any best practices with ScreenCaptureKit for protecting content without affecting AirPlay? We understand that some lower-level APIs may not be officially supported, so we would greatly appreciate guidance toward a public and future-proof implementation path. Thank you very much for your time and support. Best regards, Tony
0
0
722
Apr ’26
ScreenCaptureKit System Audio Capture Crashes with EXC_BAD_ACCESS
Bug Report: ScreenCaptureKit System Audio Capture Crashes with EXC_BAD_ACCESS Summary When using ScreenCaptureKit to capture system audio for extended periods, the application crashes with EXC_BAD_ACCESS in Swift's error handling runtime. The crash occurs in swift_getErrorValue when trying to process an error from the SCStream delegate method didStopWithError. This appears to be a framework-level issue in ScreenCaptureKit or its underlying ReplayKit implementation. Environment macOS Sonoma 14.6.1 Swift 5.8 ScreenCaptureKit framework Detailed Description Our application captures system audio using ScreenCaptureKit's audio capture capabilities. After successfully capturing for several minutes (typically after 3-4 segments of 60-second recordings), the application crashes with an EXC_BAD_ACCESS error. The crash happens when the Swift runtime attempts to process an error in the SCStreamDelegate.stream(_:didStopWithError:) method. The crash consistently occurs in swift_getErrorValue when attempting to access the class of what appears to be a null object. This suggests that the error being passed from the system framework to our delegate method is malformed or contains invalid memory. Steps to Reproduce Create an SCStream with audio capture enabled Add audio output to the stream Start capture and write audio data to disk Allow the capture to run for several minutes (3-5 minutes typically triggers the issue) The app will crash with EXC_BAD_ACCESS in swift_getErrorValue Code Sample func stream(_ stream: SCStream, didStopWithError error: Error) { print("Stream stopped with error: \(error)") // Crash occurs before this line executes } func stream(_ stream: SCStream, didOutputSampleBuffer sampleBuffer: CMSampleBuffer, of type: SCStreamOutputType) { guard type == .audio, sampleBuffer.isValid else { return } // Process audio data... } Expected Behavior The error should be properly propagated to the delegate method, allowing for graceful error handling and recovery. Actual Behavior The application crashes with EXC_BAD_ACCESS when the Swift runtime attempts to process the error in swift_getErrorValue. Crash Log Details Thread #35, queue = 'com.apple.NSXPCConnection.m-user.com.apple.replayd', stop reason = EXC_BAD_ACCESS (code=1, address=0x0) frame #0: 0x0000000194c3088c libswiftCore.dylib`swift::_swift_getClass(void const*) + 8 frame #1: 0x0000000194c30104 libswiftCore.dylib`swift_getErrorValue + 40 frame #2: 0x00000001057fba30 shadow`NewScreenCaptureService.stream(stream=0x0000600002de6700, error=Swift.Error @ 0x000000016b7b5e30) at NEW+ScreenCaptureService.swift:365:15 frame #3: 0x00000001057fc050 shadow`@objc NewScreenCaptureService.stream(_:didStopWithError:) at <compiler-generated>:0 frame #4: 0x0000000219ec5ca0 ScreenCaptureKit`-[SCStreamManager stream:didStopWithError:] + 456 frame #5: 0x00000001ca68a5cc ReplayKit`-[RPScreenRecorder stream:didStopWithError:] + 84 frame #6: 0x00000001ca696ff8 ReplayKit`-[RPDaemonProxy stream:didStopWithError:] + 224 Printing description of stream._streamQueue: error: ObjectiveC.id:4294967281:18: note: 'id' has been explicitly marked unavailable here public typealias id = AnyObject ^ error: /var/folders/v4/3xg1hmp93gjd8_xlzmryf_wm0000gn/T/expr23-dfa421..cpp:1:65: 'id' is unavailable in Swift: 'id' is not available in Swift; use 'Any' Swift._DebuggerSupport.stringForPrintObject(Swift.UnsafePointer<id>(bitPattern: 0x104ae08c0)!.pointee) ^~ ObjectiveC.id:2:18: note: 'id' has been explicitly marked unavailable here public typealias id = AnyObject ^ warning: /var/folders/v4/3xg1hmp93gjd8_xlzmryf_wm0000gn/T/expr23-dfa421..cpp:5:7: initialization of variable '$__lldb_error_result' was never used; consider replacing with assignment to '_' or removing it var $__lldb_error_result = __lldb_tmp_error ~~~~^~~~~~~~~~~~~~~~~~~~ _ Before the crash, we observed this error message in the console: [ERROR] *****SCStream*****RemoteAudioQueueOperationHandlerWithError:1015 Error received from the remote queue -16665 Additional Context The issue occurs consistently after approximately 3-4 successful audio segment recordings of 60 seconds each Commenting out custom segment rotation logic does not prevent the crash The crash involves XPC communication with Apple's ReplayKit daemon The error appears to be corrupted or malformed when crossing the XPC boundary Workarounds Attempted Added proper thread safety for all published properties using DispatchQueue.main.async Implemented more robust error handling in the delegate methods None of these approaches prevented the crash since it occurs at the Swift runtime level before our code executes. Impact This issue prevents reliable long-duration audio capture using ScreenCaptureKit. This bug significantly limits the usefulness of ScreenCaptureKit for any application requiring continuous system audio capture for more than a few minutes. Perhaps this issue might be related to a macOS bug where the system dialog indicates that the screen is being shared, even though nothing is actually being shared. Moreover, when attempting to stop sharing, nothing happens.
3
0
1.2k
Mar ’26
Mixing ScreenCaptureKit audio with microphone audio
Hi, I'm new to AVAudioEngine(and macOS programming in general). I'm trying to mix microphone audio with ScreenCaptureKit audio using AVAudioEngine without playing it back. I've created a AVAudioPlayerNode and scheduling buffers in my SCStream handler: playerNode.scheduleBuffer(samples) and have connected the playerNode to the mainMixerNode. audioEngine.connect(audioEngine.inputNode, to: audioEngine.mainMixerNode, format: micFormat) audioEngine.connect(playerNode, to: audioEngine.mainMixerNode, format: format) The problem is that mainMixerNode plays the audio to the speaker creating a feedback loop. How can I prevent the mixer output from being played back. Also: Is this the best way of mixing microphone input with some other input? I ran into AVAudioEngine's manual rendering mode, which seems like the way to go for mixing audio without playing it back. However, I couldn't figure out how to connect microphone input to the AVAudioEngine in manual rendering mode?
1
0
1.5k
Mar ’26
Unable to capture only the cursor in macOS Tahoe
Precondition: In system settings, scale the pointer size up to the max. Our SCScreenshotManager code currently works in macOS 15 and earlier to capture the cursor at it's larger size, but broke in one of the minor releases of macOS Tahoe. The error it produces now is "Failed to start stream due to audio/video capture failure". This only seems to happen with the cursor window, not any others. Another way to get the cursor is with https://developer.apple.com/documentation/appkit/nscursor/currentsystem, but that is now deprecated, which makes me think the capture of the cursor is being blocked deliberately. We see this as a critical loss of functionality for our apps, and could use guidance on what to use instead.
1
16
874
Mar ’26
ScreenCaptureKit recording output is corrupted when captureMicrophone is true
Hello everyone, I'm working on a screen recording app using ScreenCaptureKit and I've hit a strange issue. My app records the screen to an .mp4 file, and everything works perfectly until the .captureMicrophone is false In this case, I get a valid, playable .mp4 file. However, as soon as I try to enable the microphone by setting streamConfig.captureMicrophone = true, the recording seems to work, but the final .mp4 file is corrupted and cannot be played by QuickTime or any other player. This happens whether capturesAudio (app audio) is on or off. I've already added the "Privacy - Microphone Usage Description" (NSMicrophoneUsageDescription) to my Info.plist, so I don't think it's a permissions problem. I have my logic split into a ScreenRecorder class that manages state and a CaptureEngine that handles the SCStream. Here is how I'm configuring my SCStream: ScreenRecorder.swift // This is my main SCStreamConfiguration private var streamConfiguration: SCStreamConfiguration { var streamConfig = SCStreamConfiguration() // ... other HDR/preset config ... // These are the problem properties streamConfig.capturesAudio = isAudioCaptureEnabled streamConfig.captureMicrophone = isMicCaptureEnabled // breaks it if true streamConfig.excludesCurrentProcessAudio = false streamConfig.showsCursor = false if let region = selectedRegion, let display = currentDisplay { // My region/frame logic (works fine) let regionWidth = Int(region.frame.width) let regionHeight = Int(region.frame.height) streamConfig.width = regionWidth * scaleFactor streamConfig.height = regionHeight * scaleFactor // ... (sourceRect logic) ... } streamConfig.pixelFormat = kCVPixelFormatType_32BGRA streamConfig.colorSpaceName = CGColorSpace.sRGB streamConfig.minimumFrameInterval = CMTime(value: 1, timescale: 60) return streamConfig } And here is how I'm setting up the SCRecordingOutput that writes the file: ScreenRecorder.swift private func initRecordingOutput(for region: ScreenPickerManager.SelectedRegion) throws { let screeRecordingOutputURL = try RecordingWorkspace.createScreenRecordingVideoFile( in: workspaceURL, sessionIndex: sessionIndex ) let recordingConfiguration = SCRecordingOutputConfiguration() recordingConfiguration.outputURL = screeRecordingOutputURL recordingConfiguration.outputFileType = .mp4 recordingConfiguration.videoCodecType = .hevc let recordingOutput = SCRecordingOutput(configuration: recordingConfiguration, delegate: self) self.recordingOutput = recordingOutput } Finally, my CaptureEngine adds these to the SCStream: CaptureEngine.swift class CaptureEngine: NSObject, @unchecked Sendable { private(set) var stream: SCStream? private var streamOutput: CaptureEngineStreamOutput? // ... (dispatch queues) ... func startCapture(configuration: SCStreamConfiguration, filter: SCContentFilter, recordingOutput: SCRecordingOutput) async throws { let streamOutput = CaptureEngineStreamOutput() self.streamOutput = streamOutput do { stream = SCStream(filter: filter, configuration: configuration, delegate: streamOutput) // Add outputs for raw buffers (not used for file recording) try stream?.addStreamOutput(streamOutput, type: .screen, sampleHandlerQueue: videoSampleBufferQueue) try stream?.addStreamOutput(streamOutput, type: .audio, sampleHandlerQueue: audioSampleBufferQueue) try stream?.addStreamOutput(streamOutput, type: .microphone, sampleHandlerQueue: micSampleBufferQueue) // Add the file recording output try stream?.addRecordingOutput(recordingOutput) try await stream?.startCapture() } catch { logger.error("Failed to start capture: \(error.localizedDescription)") throw error } } // ... (stopCapture, etc.) ... } When I had the .captureMicrophone value to be false, I get a perfect .mp4 video playable everywhere, however, when its true, I am getting corrupted video which doesn't play at all :-
2
0
1.2k
Mar ’26
Building Real-Time Voice Input on macOS 26 with SpeechAnalyzer + ScreenCaptureKit
We built an open-source macOS menu bar app that turns speech into text and pastes it into the active app — using SpeechAnalyzer for on-device transcription, ScreenCaptureKit + Vision for screen-aware context, and FluidAudio for speaker diarization in meeting mode. Here's what we learned shipping it on macOS 26. GitHub: github.com/Marvinngg/ambient-voice Architecture The app has two modes: hotkey dictation (press to talk, release to inject) and meeting recording (continuous transcription with a floating panel). Dictation Mode Audio capture uses AVCaptureSession (more on why below). The captured audio feeds into SpeechAnalyzer via an AsyncStream: let transcriber = SpeechTranscriber( locale: locale, transcriptionOptions: [], reportingOptions: [.volatileResults, .alternativeTranscriptions], attributeOptions: [.audioTimeRange, .transcriptionConfidence] ) let analyzer = SpeechAnalyzer(modules: [transcriber]) let (inputSequence, inputBuilder) = AsyncStream.makeStream() try await analyzer.start(inputSequence: inputSequence) While recording, we capture a screenshot of the focused window using ScreenCaptureKit, run Vision OCR (VNRecognizeTextRequest), extract keywords, and inject them into SpeechAnalyzer as contextual bias: let context = AnalysisContext() context.contextualStrings[.general] = ocrKeywords try await analyzer.setContext(context) This improves accuracy for technical terms and proper nouns visible on screen. If your screen shows "SpeechAnalyzer", saying it out loud is more likely to be transcribed correctly. After transcription, an optional L2 step sends the text through a local LLM (ollama) for spoken-to-written cleanup, then CGEvent simulates Cmd+V to paste into the active app. Meeting Mode Meeting mode forks the same audio stream to two consumers: SpeechAnalyzer — real-time streaming transcription, displayed in a floating NSPanel FluidAudio buffer — accumulates 16kHz Float32 mono samples for batch speaker diarization after recording stops When the user ends the meeting, FluidAudio's performCompleteDiarization() runs on the accumulated audio. We align transcription segments with speaker segments using audioTimeRange overlap matching — each transcription segment gets assigned the speaker ID with the most time overlap. Results export to Markdown. Pitfalls We Hit on macOS 26 1. AVAudioEngine installTap doesn't fire with Bluetooth devices We started with AVAudioEngine.inputNode.installTap() for audio capture. It worked fine with built-in mics but the tap callback never fired with Bluetooth devices (tested with vivo TWS 4 Hi-Fi). Fix: switched to AVCaptureSession. The delegate callback captureOutput(_:didOutput:from:) fires reliably regardless of audio device. The tradeoff is you get CMSampleBuffer instead of AVAudioPCMBuffer, so you need a conversion step. 2. NSEvent addGlobalMonitorForEvents crashes Our global hotkey listener used NSEvent.addGlobalMonitorForEvents. On macOS 26, this crashes with a Bus error inside GlobalObserverHandler — appears to be a Swift actor runtime issue. Fix: switched to CGEventTap. Works reliably, but the callback runs on a CFRunLoop context, which Swift doesn't recognize as MainActor. 3. CGEventTap callbacks aren't on MainActor If your CGEventTap callback touches any @MainActor state, you'll get concurrency violations. The callback runs on whatever thread owns the CFRunLoop. Fix: bridge with DispatchQueue.main.async {} inside the tap callback before touching any MainActor state. 4. CGPreflightScreenCaptureAccess doesn't request permission We used CGPreflightScreenCaptureAccess() as a guard before calling ScreenCaptureKit. If it returned false, we'd bail out. The problem: this function only checks — it never triggers macOS to add your app to the Screen Recording permission list. Chicken-and-egg: you can't get permission because you never ask for it. Fix: call CGRequestScreenCaptureAccess() at app startup. This adds your app to System Settings → Screen Recording. Then let ScreenCaptureKit calls proceed without the preflight guard — SCShareableContent will also trigger the permission prompt on first use. 5. Ad-hoc signing breaks TCC permissions on every rebuild During development, codesign --sign - (ad-hoc) generates a different code directory hash on every build. macOS TCC tracks permissions by this hash, so every rebuild = new app identity = all permissions reset. Fix: sign with a stable certificate. If you have an Apple Development certificate, use that. The TeamIdentifier stays constant across rebuilds, so TCC permissions persist. We also discovered that launching via open WE.app (LaunchServices) instead of directly executing the binary is required — otherwise macOS attributes TCC permissions to Terminal, not your app. Benchmarks We ran end-to-end benchmarks on public datasets (Mac Mini M4 16GB, macOS 26): Transcription (SpeechAnalyzer, AliMeeting Chinese): • Near-field CER 34% (excluding outliers ~25%) • Far-field CER 40% (single channel, no beamforming, >30% overlap) • Processing speed 74-89x real-time Speaker diarization (FluidAudio offline): • AMI English 16 meetings: avg DER 23.2% (collar=0.25s, ignoreOverlap=True) • AliMeeting Chinese 8 meetings: DER 48.5% (including overlap regions) • Memory: RSS ~500MB, peak 730-930MB Full evaluation methodology, scripts, and raw results are in the repo. Open Source The project is MIT licensed: github.com/Marvinngg/ambient-voice It includes the macOS client (Swift 6.2, SPM), server-side distillation/training scripts (Python), and a complete evaluation framework with reproducible benchmarks. Feedback and contributions welcome.
0
0
1.1k
Mar ’26
Building Real-Time Voice Input on macOS 26 with SpeechAnalyzer + ScreenCaptureKit
We built an open-source macOS menu bar app that turns speech into text and pastes it into the active app — using SpeechAnalyzer for on-device transcription, ScreenCaptureKit + Vision for screen-aware context, and FluidAudio for speaker diarization in meeting mode. Here's what we learned shipping it on macOS 26. GitHub: github.com/Marvinngg/ambient-voice Architecture The app has two modes: hotkey dictation (press to talk, release to inject) and meeting recording (continuous transcription with a floating panel). Dictation Mode Audio capture uses AVCaptureSession (more on why below). The captured audio feeds into SpeechAnalyzer via an AsyncStream: let transcriber = SpeechTranscriber( locale: locale, transcriptionOptions: [], reportingOptions: [.volatileResults, .alternativeTranscriptions], attributeOptions: [.audioTimeRange, .transcriptionConfidence] ) let analyzer = SpeechAnalyzer(modules: [transcriber]) let (inputSequence, inputBuilder) = AsyncStream.makeStream() try await analyzer.start(inputSequence: inputSequence) While recording, we capture a screenshot of the focused window using ScreenCaptureKit, run Vision OCR (VNRecognizeTextRequest), extract keywords, and inject them into SpeechAnalyzer as contextual bias: let context = AnalysisContext() context.contextualStrings[.general] = ocrKeywords try await analyzer.setContext(context) This improves accuracy for technical terms and proper nouns visible on screen. If your screen shows "SpeechAnalyzer", saying it out loud is more likely to be transcribed correctly. After transcription, an optional L2 step sends the text through a local LLM (ollama) for spoken-to-written cleanup, then CGEvent simulates Cmd+V to paste into the active app. Meeting Mode Meeting mode forks the same audio stream to two consumers: SpeechAnalyzer — real-time streaming transcription, displayed in a floating NSPanel FluidAudio buffer — accumulates 16kHz Float32 mono samples for batch speaker diarization after recording stops When the user ends the meeting, FluidAudio's performCompleteDiarization() runs on the accumulated audio. We align transcription segments with speaker segments using audioTimeRange overlap matching — each transcription segment gets assigned the speaker ID with the most time overlap. Results export to Markdown. Pitfalls We Hit on macOS 26 1. AVAudioEngine installTap doesn't fire with Bluetooth devices We started with AVAudioEngine.inputNode.installTap() for audio capture. It worked fine with built-in mics but the tap callback never fired with Bluetooth devices (tested with vivo TWS 4 Hi-Fi). Fix: switched to AVCaptureSession. The delegate callback captureOutput(_:didOutput:from:) fires reliably regardless of audio device. The tradeoff is you get CMSampleBuffer instead of AVAudioPCMBuffer, so you need a conversion step. 2. NSEvent addGlobalMonitorForEvents crashes Our global hotkey listener used NSEvent.addGlobalMonitorForEvents. On macOS 26, this crashes with a Bus error inside GlobalObserverHandler — appears to be a Swift actor runtime issue. Fix: switched to CGEventTap. Works reliably, but the callback runs on a CFRunLoop context, which Swift doesn't recognize as MainActor. 3. CGEventTap callbacks aren't on MainActor If your CGEventTap callback touches any @MainActor state, you'll get concurrency violations. The callback runs on whatever thread owns the CFRunLoop. Fix: bridge with DispatchQueue.main.async {} inside the tap callback before touching any MainActor state. 4. CGPreflightScreenCaptureAccess doesn't request permission We used CGPreflightScreenCaptureAccess() as a guard before calling ScreenCaptureKit. If it returned false, we'd bail out. The problem: this function only checks — it never triggers macOS to add your app to the Screen Recording permission list. Chicken-and-egg: you can't get permission because you never ask for it. Fix: call CGRequestScreenCaptureAccess() at app startup. This adds your app to System Settings → Screen Recording. Then let ScreenCaptureKit calls proceed without the preflight guard — SCShareableContent will also trigger the permission prompt on first use. 5. Ad-hoc signing breaks TCC permissions on every rebuild During development, codesign --sign - (ad-hoc) generates a different code directory hash on every build. macOS TCC tracks permissions by this hash, so every rebuild = new app identity = all permissions reset. Fix: sign with a stable certificate. If you have an Apple Development certificate, use that. The TeamIdentifier stays constant across rebuilds, so TCC permissions persist. We also discovered that launching via open WE.app (LaunchServices) instead of directly executing the binary is required — otherwise macOS attributes TCC permissions to Terminal, not your app. Benchmarks We ran end-to-end benchmarks on public datasets (Mac Mini M4 16GB, macOS 26): Transcription (SpeechAnalyzer, AliMeeting Chinese): • Near-field CER 34% (excluding outliers ~25%) • Far-field CER 40% (single channel, no beamforming, >30% overlap) • Processing speed 74-89x real-time Speaker diarization (FluidAudio offline): • AMI English 16 meetings: avg DER 23.2% (collar=0.25s, ignoreOverlap=True) • AliMeeting Chinese 8 meetings: DER 48.5% (including overlap regions) • Memory: RSS ~500MB, peak 730-930MB Full evaluation methodology, scripts, and raw results are in the repo. Open Source The project is MIT licensed: github.com/Marvinngg/ambient-voice It includes the macOS client (Swift 6.2, SPM), server-side distillation/training scripts (Python), and a complete evaluation framework with reproducible benchmarks. Feedback and contributions welcome.
0
0
1.4k
Mar ’26
ScreenCaptureKit permissions lost after every build — solved by switching signing identity
Sharing a solution for a problem that took me a while to figure out. Problem: During development of a macOS 26 app that uses ScreenCaptureKit, the screen capture permissions were being reset after every build. Each time I compiled and ran the app from Xcode, I had to re-authorize screen capture in System Settings. CGPreflightScreenCaptureAccess() would return false even though I'd just granted permission minutes ago. Root cause: I was using ad-hoc code signing during development. macOS ties screen capture permissions to the app's code signing identity. With ad-hoc signing, the identity changes on every build, so the system treats each build as a "new" app. Solution: Switch to an Apple Development certificate for debug builds. In Xcode: Build Settings → Code Signing Identity → Debug → set to "Apple Development" Make sure your development team is selected After this change, the signing identity remains stable across builds, and screen capture permissions persist. This might be related to the broader issue discussed in this forum about ScreenCapture permissions disappearing — if other developers are seeing permissions vanish, it's worth checking whether the code signing identity is changing between sessions.
1
0
1.6k
Mar ’26
ScreenCapture permissions disappear and don't return
On Tahoe and earlier, ScreenCapture permissions can disappear and not return. Customers are having an issue with this disappearing and when our code executes CGRequestScreenCaptureAccess() nothing happens, the prompt does not appear. I can reproduce this by using the "-" button and removing the entry in the settings, then adding it back with the "+" button. CGPreflightScreenCaptureAccess() always returns the correct value but once the entry has been removed, CGRequestScreenCaptureAccess() requires a reboot before it will work again.
3
0
476
Mar ’26
SCStreamOutputType.audio delivers correctly-formed but all-zero PCM for VoIP background audio
Is silently zeroing .audio output for communications/VoIP-category background app audio (while leaving frame delivery, timing, and format metadata intact) expected, documented behavior on iOS? Is there an SCStreamConfiguration or SCContentFilter setting that unlocks this, or is this a known gap in the current iOS ScreenCaptureKit implementation relative to macOS (where .audio is documented/known to capture other apps' audio including calls)? A pointer to relevant documentation or a radar number would be very helpful. My test app: Rabbler Rabbler main use it turning voice to text transcriptions. ie. meeting notes. It works well using the iPhone mic or AirPods. It fails when Zoom or Teams meeting is joined. Audio is paused by a well handled interruption. But that yields lost transcription text. Environment iOS 27.0, physical device iPhone 15ProMax (not simulator) Xcode 27.0 (Build 27A266a), iPhoneOS27.0 SDK App target UIBackgroundModes: audio, screen-capture Capture another app's audio via SCStream's .audio output type, using SCContentSharingPicker for source selection, per the "Capturing screen content on iOS" sample's picker pattern. Setup (trimmed to the relevant parts) var configuration = SCContentSharingPickerConfiguration() configuration.showsMicrophoneControl = true picker.defaultConfiguration = configuration picker.add(self) picker.isActive = true picker.present() // contentSharingPicker(_:didUpdateWith:for:) -> startStream(with:) let config = SCStreamConfiguration() config.capturesAudio = true let newStream = SCStream(filter: filter, configuration: config, delegate: self) try newStream.addStreamOutput(seldlerQueue: .main) if filter.isMicrophoneEnabled { try newStream.addStreamOutputsampleHandlerQueue: .main) } try await newStream.startCapture( Sample-buffer handling, convertinmeasuring amplitude: func stream(_ stream: SCStream, deBuffer: CMSampleBuffer, of type:SCStreamOutputType) { guard sampleBuffer.isValid, tmicrophone else { return } // ... (format captured once from first buffer via CMAudioFormatDescriptionGetStream let frameCount = AVAudioFrameCount(CMSampleBufferGetNumSamples(sampleBuffer)) let pcmBuffer = AVAudioPCMBufmeCapacity: frameCount)! pcmBuffer.frameLength = frameCount let status = CMSampleBufferCost( sampleBuffer, at: 0, frameCount: Int32(frameCount), into: pcmBuffer.mutableAudioBufferList ) // status == noErr every time } Amplitude check on the resulting AVAudioPCMBuffer.floatChannelData: var peak: Float = 0, sumSquares: 0 for channel in 0..<Int(buffer.format.channelCount) { let samples = channelData[cha for frame in 0..<frameCount { let sample = samples[fram peak = max(peak, abs(sample)) sumSquares += sample * sa if sample != 0 { nonZeroCount += 1 } } } Result 1 — Music app selected as capture source (baseline, works correctly) Real audio content: writing the buffers straight to a .caf file via AVAudioFile produces a real, listenable ~9MB file matchihe capture duration (stereo, 48kHz, Float32). Confirmed by ear. Result 2 — Zoom call selected as capture source (fails silently) SCStream delivers buffers continuously and correctly-formed — same format every time (2 ch, 48000 Hz, Float32, deinterleaved)atus == noErr fromCMSampleBufferCopyPCMDataIntoAudioBufferList every time. But every sample is exactly zero: [2:17:19 PM] ScreenCaptureService: .audio first buffer format — <AVAudioFormat 0x12063fb60: 2ch, 48000 Hz, Float32, deinterlea [2:17:19 PM] ScreenCaptureService: .audio buffer #1 — peak=0.0 rms=0.0 nonZero=0/1920 [2:17:19 PM] ScreenCaptureService0.0 rms=0.0 nonZero=0/1920 ... [2:17:21 PM] ScreenCaptureServicek=0.0 rms=0.0 nonZero=0/1920 [2:18:27 PM] ScreenCaptureService: .audio buffer #3400 — peak=0.0 rms=0.0 nonZero=0/1920 That's peak=0.0/rms=0.0/0 nonZero samples across every single one of 3400+ consecutive buffersover ~70 seconds of an active Zoo Writing these buffers to a .caffile produces a valid, correctly-sized, completely silent audio file — not corrupted, not empty, genuinely all zeros. Control test — .microphone in theall With filter.isMicrophoneEnabled = on the same SCStream, during thesame Zoom call, correctly captures the local user's own voice (confirmed by ear from the resulting file) — even though the call is silently holding exclusive access to the mic hardware from Rabbler's own AVAudioEngine.inputNode tap (which gets interrupted, as expected). This rules out a session-wide permission failure -.micophone clearly has real access to audio in this exact session; .audio does not, specifically for this source. evidence now looks like this iOS 27 ScreenCaptureKit │ ├── SomaFM playback │ └── .audio → REAL PCM ✓ │ ├── Zoom remote audio │ └── .audio → ZERO PCM ✗ │ └── Teams remote audio └── .audio → ZERO PCM ✗
Replies
2
Boosts
0
Views
529
Activity
1d
ScreenCaptureKit authorization fails after tccd exhausts file descriptors (FB24757092)
I’m seeking DTS guidance on supported ScreenCaptureKit usage and diagnostics for an intermittent authorization failure, filed as FB24757092. Captured evidence (my incident) On macOS 26.6.2 (25G83), Apple silicon, an already-authorized custom Apple Development-signed AltTabDebug build encountered renewed Screen Recording prompts. Unified logs show root tccd exhausting file descriptors during authorization. Three failing tccd processes each reported 255 total descriptors, with 250 unique descriptors referring to the same app executable. SecStaticCodeCreateWithPath failed with error 100024 (UNIX[Too many open files]); tccd could not match the existing kTCCServiceScreenCapture code requirement, and replayd reported TCC Disallow / user denied for captureScreenshot. Later checks allowed the same running app again. This confirms resource exhaustion and authorization failures. It does not establish why descriptors accumulated, a persistent leak mechanism, a per-capture leak rate, or a deterministic reproducer. The reported load spike and roughly one-second display freeze have an uncertain causal relationship to these failures. I have not established reproduction on macOS 27 or descriptor exhaustion in an unmodified release build. No incident-time sysdiagnose was collected. Separate upstream observations The AltTab maintainer reported testing the signed release in /Applications on the same OS build: 446 screenshots produced 1,350 ScreenCapture authorization requests, with repeated code-signature validation; approximately 23 ms of root-tccd CPU per thumbnail was reported. These are the maintainer’s measurements, not independently repeated by me, and do not measure descriptor growth per capture. https://github.com/lwouis/alt-tab-macos/issues/6025 https://github.com/lwouis/alt-tab-macos/issues/6025#issuecomment-5640382131 Questions Is there a supported ScreenCaptureKit capture pattern, request concurrency/rate guidance, or recovery strategy to reduce repeated authorization work and avoid renewed prompts when code validation fails from transient resource exhaustion? How should an app distinguish a genuine permission denial from an unavailable/failed authorization service, without repeatedly requesting permission? Which logging profile, trace, or incident-time diagnostics should we collect to correlate capture submissions/completions with tccd descriptor lifetime and isolate the accumulation mechanism? FB24757092 contains the focused timeline, descriptor dumps, prompt screenshot, and separately attributed maintainer findings. Raw diagnostics and private system/account information are intentionally omitted here. I built a standalone Swift/AppKit probe that uses one retained SCContentFilter per batch, logs submissions and actual callbacks, bounds outstanding requests, stops new submissions on the first error, and discards images. On macOS 27.0 (26A428), its ad-hoc-signed build captured its own window successfully in 446-request captureScreenshot batches at concurrency 1 and 4. This validates the harness, not reproduction of the original failure. The code-level form requests a focused project demonstrating the issue; this probe does not yet reproduce exhaustion. Could DTS advise the next isolation step and whether a private support case is appropriate for the existing evidence?
Replies
1
Boosts
0
Views
311
Activity
1d
ScreenCaptureKit on iPadOS 27 is capped at 60 fps on 120Hz ProMotion devices, even with minimumFrameInterval set to 1/120
On an iPad Pro 11-inch (M4) running iPadOS 27.0 (24A437), ScreenCaptureKit delivers a maximum of 60 frames per second when capturing the entire screen, even while an app is rendering at 120 fps (confirmed with the Metal Performance HUD). What I tested: Default configuration: exactly 60 fps, with every frame timestamp spaced 16.67ms apart. Setting minimumFrameInterval to 1/120 and queueDepth to 8, both before starting the stream and through updateConfiguration after it started: the values are accepted and read back correctly, but delivery stays at exactly 60 fps. Smaller output sizes (1/4 and 1/8 of native resolution): still 60 fps. ReplayKit broadcast upload extension: also exactly 60 fps. Also, minimumFrameInterval and queueDepth are documented as available on iOS/iPadOS 27, but the iOS 27 SDK marks them as unavailable. Request: please allow ScreenCaptureKit to capture at the display's full refresh rate (up to 120 fps) on ProMotion devices when minimumFrameInterval asks for it, and make minimumFrameInterval available in the iOS SDK.
Replies
0
Boosts
0
Views
231
Activity
1d
Get Desktop background image
In a WWDC 2019 "Advances in macOS Security" at 18:40 there is the following code func getDesktopWindowIds() -> [CGWindowID] { let windows = CGWindowListCopyWindowInfo([.optionOnScreenOnly], kCGNullWindowID)! as! [[String: AnyObject]] let DesktopWindowLevel = CGWindowLevelForKey(.desktopWindow)-1 let DesktopWindows = windows.filter { let windowLevel = $0[kCGWindowLayer as String] as! CGWindowLevel return windowLevel == desktopWindowLevel } return desktopWindows.map { $0[kCGWindowNumber as String] as! CGWindowID } } to find the CGWindowID of the Desktop background. This works, but when you then try to get the CGImage for that CGWindowID with let cgImage = CGWindowListCreateImage(CGRectNull, [.optionIncludingWindow], cgWin, [.bestResolution]) cgImage does get a reference, however it's just a gray image. Not the Desktop picture. It's clear from the documentation that ScreenCaptureKit should be used. However, if used I get multiple warnings to the user, the most concerning one states: "App" would like to record this computer's screen and audio. This is not true! I do nothing with audio and to say a capture is a recording is also misleading. Is there way to achieve what use to work before macOS 27? Or a way to change/avoid this misleading warning? Is there a reason why this warning is needed for capturing the Desktop background where before it was explicitly allowed?
Replies
0
Boosts
0
Views
418
Activity
3d
iOS 27: SCStreamConfiguration.excludesCurrentProcessAudio has no effect (0.0 dB separation) - what is the supported way to exclude our own audio?
On iOS, SCStreamConfiguration.excludesCurrentProcessAudio appears to do nothing. Audio produced by our own process is captured at full level, so there is currently no way to capture device audio while excluding what our app is playing. Measurement (deterministic probe, 2026-08-08): Our app plays a 1 kHz tone at -4.9 dBFS RMS from its own process while capturing device audio with SCStreamConfiguration.capturesAudio = true and excludesCurrentProcessAudio = true. The configuration in force is read back from the stream and logged, so we know the flag is actually set. The captured audio contained our own tone at -4.9 dBFS peak. Separation = 0.0 dB. 1,573 audio sample buffers analyzed over 31.5 s (16 kHz mono, 50 buffers/s), zero analysis failures, and the run was reproduced twice about 3 hours apart with byte-identical verdicts. Environment for that run: iPhone 16 Pro (iPhone17,1), iOS 27.0, built with Xcode 27 beta 4 (27A5228h) against the iOS 27 SDK, installed directly from Xcode. Still reproducing on the current build: on iOS 27.0 (24A5418b) our shipping capture path still receives our own playback. In production we now have to cancel it ourselves - we align our playback buffer to the captured stream by cross-correlation and subtract it. The correlation between the captured signal and our own playback sits at |r| = 0.65-0.85 in the affected segments, i.e. the capture is dominated by a copy of our own output, exactly what excludesCurrentProcessAudio is supposed to remove. Doing this subtraction in-process costs real CPU and only works while we can hold a delay lock. Why this is blocking: RPSampleBufferType.audioApp is deprecated as of iOS 27 and the documentation points to ScreenCaptureKit as the replacement. With excludesCurrentProcessAudio non-functional there is no supported path on iOS to capture device audio while excluding one's own process. Our app is a real-time dubbing app - it captures foreign-language audio, transcribes it, and plays back a translated voice - so our own output re-entering the capture is fed straight back into transcription and corrupts the session. My questions: (1) Is excludesCurrentProcessAudio expected to be functional on iOS 27, or is it macOS-only in practice? The documentation does not mark it as unavailable on iOS. (2) If it is expected to work, is there anything the app must do besides setting it on the SCStreamConfiguration used to start the stream? (3) If it is not going to work on iOS, what is the supported way to exclude the current process's audio from a ScreenCaptureKit capture, now that RPSampleBufferType.audioApp is deprecated? Filed as FB24170972 on 2026-08-08, with the full JSON event logs from both probe runs attached. There has been no response on the Feedback, which is why I am raising it here.
Replies
5
Boosts
1
Views
516
Activity
1w
Is there a supported way to read on-screen text with the user's consent? (worker-safety / income-transparency app)
Hello, I am an independent developer in Argentina building an app for rideshare drivers, and I would like to know what the supported approach is on iOS before I build the wrong thing. The problem When a trip offer appears, the driver sees a fare, a distance and a duration. What they do not see is what is left after the cost of running their own car — fuel, maintenance, and very often the weekly rent they pay for the vehicle. Here those costs are high and change constantly, and drivers routinely accept trips that lose them money without realising it. They have about three seconds to decide, while driving. Our app reads the numbers already visible on the driver's own screen, subtracts that particular driver's cost per kilometre, and shows one figure: what this trip actually leaves them per hour. In our own field measurements with two drivers over several weeks, being able to see that number raised their hourly earnings and — just as important — kept their eyes off mental arithmetic at the wheel. What we will not do, on purpose I want to be explicit about this, because I know other tools in this category cut corners: We never ask for the driver's Uber or DiDi username or password, and we never connect to or automate their account. Some tools do, and that puts the driver's account — their livelihood — at risk of deactivation. We will not build that. We never accept or decline a trip for the driver. The decision and the tap are always theirs. We never store passenger data: no name, no rating, no photo, no exact address. Text recognition runs entirely on device, offline. The captured image is discarded immediately and never leaves the phone or reaches any server. No advertising, no data sales, no third-party trackers. The app does not read anything the driver is not already looking at, it only reads while the driver has explicitly turned it on for their shift, and it exists for one purpose: so that someone working long hours knows what an hour of their work is actually worth. My question On Android this is done with MediaProjection, with explicit user consent each session and a persistent system indicator. On iOS I understand a Broadcast Upload Extension is not intended for background processing, and that there is no overlay across apps. Is there any supported path on iOS for an app to read, with the user's explicit and repeated consent, text the user is already looking at on their own screen, in order to give that same user an immediate assessment of their own work? Accessibility APIs, a system extension point, anything. If there is no supported path, I would rather know now and design the iOS version around what is allowed, instead of shipping something that gets rejected or that quietly breaks the rules. Thank you.
Replies
1
Boosts
0
Views
380
Activity
3w
iOS 27: ScreenCaptureKit requires UIBackgroundModes 'screen-capture', but App StoiOS 27: ScreenCaptureKit requires UIBackgroundModes 'screen-capture', but App Store Connect rejects that valuere Connect rejects that value
On iOS, ScreenCaptureKit terminates an SCStream when the app is backgrounded unless the app declares UIBackgroundModes: screen-capture. The delegate reports SCStreamError code -3824 (SCStreamError.Code.missingBackgroundMode). Adding screen-capture to UIBackgroundModes - the value Apple's own iOS 27 ScreenCaptureKit sample code declares - makes App Store Connect reject the upload: error: exportArchive Invalid Info.plist value. The Info.plist key UIBackgroundModes contains an invalid value: 'screen-capture'. So the app cannot be distributed at all, not even to TestFlight internal testers. We have ruled out simply keeping the process alive: running a continuous silent AVAudioEngine under the audio background mode keeps the app running but does not prevent -3824, which is consistent with the framework performing an explicit background-mode check rather than the stream dying from process suspension. One observation that may explain why this has gone unnoticed: Apple's sample is installed directly from Xcode and therefore never passes through App Store Connect's upload validation, so a mismatch between the sample's Info.plist and App Store Connect's allowlist would not be visible when testing the sample internally. My question is which of these three describes the actual status of screen-capture for third-party iOS apps: (1) App Store Connect's allowlist has not yet been updated for iOS 27, and this will resolve on its own. (2) screen-capture requires an entitlement or a specially provisioned profile we have not requested. If so, which one, and how is it requested? (3) screen-capture is restricted to Apple's own or system applications and is not available to third-party App Store apps by design. I could not find screen-capture documented on the general UIBackgroundModes page, which is why I cannot tell these apart. Filed as FB24169650. Device: iPhone 16 Pro, iOS 27.0 Built against the iOS 27 SDK, deployment target 17.0.
Replies
4
Boosts
0
Views
575
Activity
Aug ’26
Public API to silently query "Remote Desktop" TCC authorization status (without triggering a system prompt)
Product area macOS / Privacy & Security / ScreenCaptureKit / Core Graphics Environment macOS 27 Beta 4 (build: fill in your exact build number, e.g. 27A5xxx) Xcode 26.5 / SDK 260500 (adjust to match what you actually built with) App holds the com.apple.developer.persistent-content-capture entitlement (approved via Apple's request form), targeting macOS 14.4+ Summary Our app is a remote-support/remote-control tool (screen viewing + control), comparable to VNC-style products. On macOS 27, we've found that System Settings > Privacy & Security now shows a "Remote Desktop" entry that is distinct from "Screen & System Audio Recording" — granting one does not affect the other. We need a way to check, at any time, whether our app currently has "Remote Desktop" authorization, without causing the system to show a permission-request alert as a side effect. We have not found a documented, public API that does this. What we've tried CGPreflightScreenCaptureAccess() Confirmed via a controlled test on-device: granting only "Remote Desktop" leaves this API returning false; granting only "Screen & System Audio Recording" makes it return true. So this API appears to reflect kTCCServiceScreenCapture only, and does not reflect the "Remote Desktop" permission at all. ScreenCaptureKit (SCShareableContent, e.g. via a refreshAvailableContentWithCompletionHandler:-style call) This call does appear to interact with the "Remote Desktop" permission — but calling it triggers a real system consent alert every time we call it, even when we only intend to read the current status, not request it. This makes it unusable for passive/background status polling (e.g. to decide what to show in our own onboarding UI without surprising the user with an OS-level prompt). We are intentionally not reading /Library/Application Support/com.apple.TCC/TCC.db directly — we understand this is a private, undocumented database and want a supported API instead. Sample code illustrating both attempts // Attempt 1: CGPreflightScreenCaptureAccess — does not reflect Remote Desktop grant BOOL preflightResult = CGPreflightScreenCaptureAccess(); // preflightResult stays NO even after the user grants "Remote Desktop" in // System Settings > Privacy & Security > Remote Desktop. // It correctly flips to YES only when "Screen & System Audio Recording" is granted. // Attempt 2: ScreenCaptureKit-based check — reflects it, but prompts every time SCShareableContent... // (via our wrapper) refreshAvailableContentWithCompletionHandler: // This call appears to influence/query the Remote Desktop TCC entry, but the OS // shows a permission alert as a side effect of the call itself, even when we only // want to read the current authorization state. Question Is there a public, documented API equivalent to CGPreflightScreenCaptureAccess() — i.e., a read-only, non-prompting status check — for the new "Remote Desktop" privacy category introduced around macOS 26/27? Is com.apple.developer.persistent-content-capture actually the entitlement that governs this new "Remote Desktop" category, or is it unrelated? Apple's own documentation describes this entitlement purely in terms of "persistent access to screen capture" for VNC apps, with no mention of a distinct "Remote Desktop" permission surface — we'd like to confirm whether that description is still accurate on macOS 26/27, or whether the underlying TCC service (kTCCServiceRemoteDesktop, which we found via TCC.db schema inspection only, not public docs) has been intentionally split out. If no such API exists yet, is this planned, and is there a recommended interim approach for apps that need to know this state before deciding whether to show their own onboarding/permission UI?
Replies
2
Boosts
0
Views
580
Activity
Aug ’26
SCScreenshotManager.captureImage and display sleep
I am using SCScreenshotManager.captureImage to capture image of an app window. i have the sleep settings that sleeps the display but not the system. when the display sleeps taking screenshot using SCScreenshotManager.captureImage does not work. Is there a way i can take screenshot of window when the display sleeps? Thanks
Replies
4
Boosts
0
Views
251
Activity
May ’26
ScreenCaptureKit stops capturing after ~10–15 minutes unexpectedly
When using the built-in macOS screen recording feature, the recording stops automatically after approximately 10–15 minutes without any warning or error message. No manual stop action is performed. The recording simply ends silently. The same issue also occurs when using ScreenCaptureKit in a custom application, which suggests this may be a system-level issue related to screen capture rather than an app-specific problem. This issue is reproducible and happens consistently after running for a period of time.
Replies
0
Boosts
0
Views
534
Activity
Apr ’26
ScreenCaptureKit stops capturing after ~10–15 minutes unexpectedly
When using the built-in macOS screen recording feature, the recording stops automatically after approximately 10–15 minutes without any warning or error message. No manual stop action is performed. The recording simply ends silently. The same issue also occurs when using ScreenCaptureKit in a custom application, which suggests this may be a system-level issue related to screen capture rather than an app-specific problem. This issue is reproducible and happens consistently after running for a period of time.
Replies
0
Boosts
0
Views
553
Activity
Apr ’26
Technical guidance request: native screen capture protection on macOS with Flutter while allowing AirPlay
Hello Apple Developer Support, I am reaching out for technical guidance regarding screen capture protection behavior on macOS. We are building a desktop application using Flutter running on macOS, and we have implemented native Swift code inside the macOS Runner in order to protect sensitive content from screen recording and screen sharing. Our current implementation relies on native window-level protection and display state handling from Swift, while the main UI remains rendered by Flutter. The main challenge we are facing is the following: we need to keep a strong native anti-recording protection on macOS the application is heavily used with AirPlay and screen mirroring currently, AirPlay / mirroring is often interpreted by the system similarly to screen capture or screen recording this causes our protected content to be replaced by a gray or blank area even during legitimate AirPlay usage In practice, we would like to allow: AirPlay legitimate external display / mirroring usage while still preventing: screen recording screen sharing unauthorized screen capture We would like to know whether Apple recommends an official supported approach for this use case, preferably using public APIs. More specifically: Is there an officially supported way on macOS to distinguish AirPlay mirroring from screen recording / screen sharing? Is "NSWindow.sharingType" the recommended public API for this scenario? Is there a recommended approach when the UI surface is rendered through Flutter / Metal? Are there any best practices with ScreenCaptureKit for protecting content without affecting AirPlay? We understand that some lower-level APIs may not be officially supported, so we would greatly appreciate guidance toward a public and future-proof implementation path. Thank you very much for your time and support. Best regards, Tony
Replies
0
Boosts
0
Views
722
Activity
Apr ’26
ScreenCaptureKit System Audio Capture Crashes with EXC_BAD_ACCESS
Bug Report: ScreenCaptureKit System Audio Capture Crashes with EXC_BAD_ACCESS Summary When using ScreenCaptureKit to capture system audio for extended periods, the application crashes with EXC_BAD_ACCESS in Swift's error handling runtime. The crash occurs in swift_getErrorValue when trying to process an error from the SCStream delegate method didStopWithError. This appears to be a framework-level issue in ScreenCaptureKit or its underlying ReplayKit implementation. Environment macOS Sonoma 14.6.1 Swift 5.8 ScreenCaptureKit framework Detailed Description Our application captures system audio using ScreenCaptureKit's audio capture capabilities. After successfully capturing for several minutes (typically after 3-4 segments of 60-second recordings), the application crashes with an EXC_BAD_ACCESS error. The crash happens when the Swift runtime attempts to process an error in the SCStreamDelegate.stream(_:didStopWithError:) method. The crash consistently occurs in swift_getErrorValue when attempting to access the class of what appears to be a null object. This suggests that the error being passed from the system framework to our delegate method is malformed or contains invalid memory. Steps to Reproduce Create an SCStream with audio capture enabled Add audio output to the stream Start capture and write audio data to disk Allow the capture to run for several minutes (3-5 minutes typically triggers the issue) The app will crash with EXC_BAD_ACCESS in swift_getErrorValue Code Sample func stream(_ stream: SCStream, didStopWithError error: Error) { print("Stream stopped with error: \(error)") // Crash occurs before this line executes } func stream(_ stream: SCStream, didOutputSampleBuffer sampleBuffer: CMSampleBuffer, of type: SCStreamOutputType) { guard type == .audio, sampleBuffer.isValid else { return } // Process audio data... } Expected Behavior The error should be properly propagated to the delegate method, allowing for graceful error handling and recovery. Actual Behavior The application crashes with EXC_BAD_ACCESS when the Swift runtime attempts to process the error in swift_getErrorValue. Crash Log Details Thread #35, queue = 'com.apple.NSXPCConnection.m-user.com.apple.replayd', stop reason = EXC_BAD_ACCESS (code=1, address=0x0) frame #0: 0x0000000194c3088c libswiftCore.dylib`swift::_swift_getClass(void const*) + 8 frame #1: 0x0000000194c30104 libswiftCore.dylib`swift_getErrorValue + 40 frame #2: 0x00000001057fba30 shadow`NewScreenCaptureService.stream(stream=0x0000600002de6700, error=Swift.Error @ 0x000000016b7b5e30) at NEW+ScreenCaptureService.swift:365:15 frame #3: 0x00000001057fc050 shadow`@objc NewScreenCaptureService.stream(_:didStopWithError:) at <compiler-generated>:0 frame #4: 0x0000000219ec5ca0 ScreenCaptureKit`-[SCStreamManager stream:didStopWithError:] + 456 frame #5: 0x00000001ca68a5cc ReplayKit`-[RPScreenRecorder stream:didStopWithError:] + 84 frame #6: 0x00000001ca696ff8 ReplayKit`-[RPDaemonProxy stream:didStopWithError:] + 224 Printing description of stream._streamQueue: error: ObjectiveC.id:4294967281:18: note: 'id' has been explicitly marked unavailable here public typealias id = AnyObject ^ error: /var/folders/v4/3xg1hmp93gjd8_xlzmryf_wm0000gn/T/expr23-dfa421..cpp:1:65: 'id' is unavailable in Swift: 'id' is not available in Swift; use 'Any' Swift._DebuggerSupport.stringForPrintObject(Swift.UnsafePointer<id>(bitPattern: 0x104ae08c0)!.pointee) ^~ ObjectiveC.id:2:18: note: 'id' has been explicitly marked unavailable here public typealias id = AnyObject ^ warning: /var/folders/v4/3xg1hmp93gjd8_xlzmryf_wm0000gn/T/expr23-dfa421..cpp:5:7: initialization of variable '$__lldb_error_result' was never used; consider replacing with assignment to '_' or removing it var $__lldb_error_result = __lldb_tmp_error ~~~~^~~~~~~~~~~~~~~~~~~~ _ Before the crash, we observed this error message in the console: [ERROR] *****SCStream*****RemoteAudioQueueOperationHandlerWithError:1015 Error received from the remote queue -16665 Additional Context The issue occurs consistently after approximately 3-4 successful audio segment recordings of 60 seconds each Commenting out custom segment rotation logic does not prevent the crash The crash involves XPC communication with Apple's ReplayKit daemon The error appears to be corrupted or malformed when crossing the XPC boundary Workarounds Attempted Added proper thread safety for all published properties using DispatchQueue.main.async Implemented more robust error handling in the delegate methods None of these approaches prevented the crash since it occurs at the Swift runtime level before our code executes. Impact This issue prevents reliable long-duration audio capture using ScreenCaptureKit. This bug significantly limits the usefulness of ScreenCaptureKit for any application requiring continuous system audio capture for more than a few minutes. Perhaps this issue might be related to a macOS bug where the system dialog indicates that the screen is being shared, even though nothing is actually being shared. Moreover, when attempting to stop sharing, nothing happens.
Replies
3
Boosts
0
Views
1.2k
Activity
Mar ’26
Mixing ScreenCaptureKit audio with microphone audio
Hi, I'm new to AVAudioEngine(and macOS programming in general). I'm trying to mix microphone audio with ScreenCaptureKit audio using AVAudioEngine without playing it back. I've created a AVAudioPlayerNode and scheduling buffers in my SCStream handler: playerNode.scheduleBuffer(samples) and have connected the playerNode to the mainMixerNode. audioEngine.connect(audioEngine.inputNode, to: audioEngine.mainMixerNode, format: micFormat) audioEngine.connect(playerNode, to: audioEngine.mainMixerNode, format: format) The problem is that mainMixerNode plays the audio to the speaker creating a feedback loop. How can I prevent the mixer output from being played back. Also: Is this the best way of mixing microphone input with some other input? I ran into AVAudioEngine's manual rendering mode, which seems like the way to go for mixing audio without playing it back. However, I couldn't figure out how to connect microphone input to the AVAudioEngine in manual rendering mode?
Replies
1
Boosts
0
Views
1.5k
Activity
Mar ’26
Unable to capture only the cursor in macOS Tahoe
Precondition: In system settings, scale the pointer size up to the max. Our SCScreenshotManager code currently works in macOS 15 and earlier to capture the cursor at it's larger size, but broke in one of the minor releases of macOS Tahoe. The error it produces now is "Failed to start stream due to audio/video capture failure". This only seems to happen with the cursor window, not any others. Another way to get the cursor is with https://developer.apple.com/documentation/appkit/nscursor/currentsystem, but that is now deprecated, which makes me think the capture of the cursor is being blocked deliberately. We see this as a critical loss of functionality for our apps, and could use guidance on what to use instead.
Replies
1
Boosts
16
Views
874
Activity
Mar ’26
ScreenCaptureKit recording output is corrupted when captureMicrophone is true
Hello everyone, I'm working on a screen recording app using ScreenCaptureKit and I've hit a strange issue. My app records the screen to an .mp4 file, and everything works perfectly until the .captureMicrophone is false In this case, I get a valid, playable .mp4 file. However, as soon as I try to enable the microphone by setting streamConfig.captureMicrophone = true, the recording seems to work, but the final .mp4 file is corrupted and cannot be played by QuickTime or any other player. This happens whether capturesAudio (app audio) is on or off. I've already added the "Privacy - Microphone Usage Description" (NSMicrophoneUsageDescription) to my Info.plist, so I don't think it's a permissions problem. I have my logic split into a ScreenRecorder class that manages state and a CaptureEngine that handles the SCStream. Here is how I'm configuring my SCStream: ScreenRecorder.swift // This is my main SCStreamConfiguration private var streamConfiguration: SCStreamConfiguration { var streamConfig = SCStreamConfiguration() // ... other HDR/preset config ... // These are the problem properties streamConfig.capturesAudio = isAudioCaptureEnabled streamConfig.captureMicrophone = isMicCaptureEnabled // breaks it if true streamConfig.excludesCurrentProcessAudio = false streamConfig.showsCursor = false if let region = selectedRegion, let display = currentDisplay { // My region/frame logic (works fine) let regionWidth = Int(region.frame.width) let regionHeight = Int(region.frame.height) streamConfig.width = regionWidth * scaleFactor streamConfig.height = regionHeight * scaleFactor // ... (sourceRect logic) ... } streamConfig.pixelFormat = kCVPixelFormatType_32BGRA streamConfig.colorSpaceName = CGColorSpace.sRGB streamConfig.minimumFrameInterval = CMTime(value: 1, timescale: 60) return streamConfig } And here is how I'm setting up the SCRecordingOutput that writes the file: ScreenRecorder.swift private func initRecordingOutput(for region: ScreenPickerManager.SelectedRegion) throws { let screeRecordingOutputURL = try RecordingWorkspace.createScreenRecordingVideoFile( in: workspaceURL, sessionIndex: sessionIndex ) let recordingConfiguration = SCRecordingOutputConfiguration() recordingConfiguration.outputURL = screeRecordingOutputURL recordingConfiguration.outputFileType = .mp4 recordingConfiguration.videoCodecType = .hevc let recordingOutput = SCRecordingOutput(configuration: recordingConfiguration, delegate: self) self.recordingOutput = recordingOutput } Finally, my CaptureEngine adds these to the SCStream: CaptureEngine.swift class CaptureEngine: NSObject, @unchecked Sendable { private(set) var stream: SCStream? private var streamOutput: CaptureEngineStreamOutput? // ... (dispatch queues) ... func startCapture(configuration: SCStreamConfiguration, filter: SCContentFilter, recordingOutput: SCRecordingOutput) async throws { let streamOutput = CaptureEngineStreamOutput() self.streamOutput = streamOutput do { stream = SCStream(filter: filter, configuration: configuration, delegate: streamOutput) // Add outputs for raw buffers (not used for file recording) try stream?.addStreamOutput(streamOutput, type: .screen, sampleHandlerQueue: videoSampleBufferQueue) try stream?.addStreamOutput(streamOutput, type: .audio, sampleHandlerQueue: audioSampleBufferQueue) try stream?.addStreamOutput(streamOutput, type: .microphone, sampleHandlerQueue: micSampleBufferQueue) // Add the file recording output try stream?.addRecordingOutput(recordingOutput) try await stream?.startCapture() } catch { logger.error("Failed to start capture: \(error.localizedDescription)") throw error } } // ... (stopCapture, etc.) ... } When I had the .captureMicrophone value to be false, I get a perfect .mp4 video playable everywhere, however, when its true, I am getting corrupted video which doesn't play at all :-
Replies
2
Boosts
0
Views
1.2k
Activity
Mar ’26
Building Real-Time Voice Input on macOS 26 with SpeechAnalyzer + ScreenCaptureKit
We built an open-source macOS menu bar app that turns speech into text and pastes it into the active app — using SpeechAnalyzer for on-device transcription, ScreenCaptureKit + Vision for screen-aware context, and FluidAudio for speaker diarization in meeting mode. Here's what we learned shipping it on macOS 26. GitHub: github.com/Marvinngg/ambient-voice Architecture The app has two modes: hotkey dictation (press to talk, release to inject) and meeting recording (continuous transcription with a floating panel). Dictation Mode Audio capture uses AVCaptureSession (more on why below). The captured audio feeds into SpeechAnalyzer via an AsyncStream: let transcriber = SpeechTranscriber( locale: locale, transcriptionOptions: [], reportingOptions: [.volatileResults, .alternativeTranscriptions], attributeOptions: [.audioTimeRange, .transcriptionConfidence] ) let analyzer = SpeechAnalyzer(modules: [transcriber]) let (inputSequence, inputBuilder) = AsyncStream.makeStream() try await analyzer.start(inputSequence: inputSequence) While recording, we capture a screenshot of the focused window using ScreenCaptureKit, run Vision OCR (VNRecognizeTextRequest), extract keywords, and inject them into SpeechAnalyzer as contextual bias: let context = AnalysisContext() context.contextualStrings[.general] = ocrKeywords try await analyzer.setContext(context) This improves accuracy for technical terms and proper nouns visible on screen. If your screen shows "SpeechAnalyzer", saying it out loud is more likely to be transcribed correctly. After transcription, an optional L2 step sends the text through a local LLM (ollama) for spoken-to-written cleanup, then CGEvent simulates Cmd+V to paste into the active app. Meeting Mode Meeting mode forks the same audio stream to two consumers: SpeechAnalyzer — real-time streaming transcription, displayed in a floating NSPanel FluidAudio buffer — accumulates 16kHz Float32 mono samples for batch speaker diarization after recording stops When the user ends the meeting, FluidAudio's performCompleteDiarization() runs on the accumulated audio. We align transcription segments with speaker segments using audioTimeRange overlap matching — each transcription segment gets assigned the speaker ID with the most time overlap. Results export to Markdown. Pitfalls We Hit on macOS 26 1. AVAudioEngine installTap doesn't fire with Bluetooth devices We started with AVAudioEngine.inputNode.installTap() for audio capture. It worked fine with built-in mics but the tap callback never fired with Bluetooth devices (tested with vivo TWS 4 Hi-Fi). Fix: switched to AVCaptureSession. The delegate callback captureOutput(_:didOutput:from:) fires reliably regardless of audio device. The tradeoff is you get CMSampleBuffer instead of AVAudioPCMBuffer, so you need a conversion step. 2. NSEvent addGlobalMonitorForEvents crashes Our global hotkey listener used NSEvent.addGlobalMonitorForEvents. On macOS 26, this crashes with a Bus error inside GlobalObserverHandler — appears to be a Swift actor runtime issue. Fix: switched to CGEventTap. Works reliably, but the callback runs on a CFRunLoop context, which Swift doesn't recognize as MainActor. 3. CGEventTap callbacks aren't on MainActor If your CGEventTap callback touches any @MainActor state, you'll get concurrency violations. The callback runs on whatever thread owns the CFRunLoop. Fix: bridge with DispatchQueue.main.async {} inside the tap callback before touching any MainActor state. 4. CGPreflightScreenCaptureAccess doesn't request permission We used CGPreflightScreenCaptureAccess() as a guard before calling ScreenCaptureKit. If it returned false, we'd bail out. The problem: this function only checks — it never triggers macOS to add your app to the Screen Recording permission list. Chicken-and-egg: you can't get permission because you never ask for it. Fix: call CGRequestScreenCaptureAccess() at app startup. This adds your app to System Settings → Screen Recording. Then let ScreenCaptureKit calls proceed without the preflight guard — SCShareableContent will also trigger the permission prompt on first use. 5. Ad-hoc signing breaks TCC permissions on every rebuild During development, codesign --sign - (ad-hoc) generates a different code directory hash on every build. macOS TCC tracks permissions by this hash, so every rebuild = new app identity = all permissions reset. Fix: sign with a stable certificate. If you have an Apple Development certificate, use that. The TeamIdentifier stays constant across rebuilds, so TCC permissions persist. We also discovered that launching via open WE.app (LaunchServices) instead of directly executing the binary is required — otherwise macOS attributes TCC permissions to Terminal, not your app. Benchmarks We ran end-to-end benchmarks on public datasets (Mac Mini M4 16GB, macOS 26): Transcription (SpeechAnalyzer, AliMeeting Chinese): • Near-field CER 34% (excluding outliers ~25%) • Far-field CER 40% (single channel, no beamforming, >30% overlap) • Processing speed 74-89x real-time Speaker diarization (FluidAudio offline): • AMI English 16 meetings: avg DER 23.2% (collar=0.25s, ignoreOverlap=True) • AliMeeting Chinese 8 meetings: DER 48.5% (including overlap regions) • Memory: RSS ~500MB, peak 730-930MB Full evaluation methodology, scripts, and raw results are in the repo. Open Source The project is MIT licensed: github.com/Marvinngg/ambient-voice It includes the macOS client (Swift 6.2, SPM), server-side distillation/training scripts (Python), and a complete evaluation framework with reproducible benchmarks. Feedback and contributions welcome.
Replies
0
Boosts
0
Views
1.1k
Activity
Mar ’26
Building Real-Time Voice Input on macOS 26 with SpeechAnalyzer + ScreenCaptureKit
We built an open-source macOS menu bar app that turns speech into text and pastes it into the active app — using SpeechAnalyzer for on-device transcription, ScreenCaptureKit + Vision for screen-aware context, and FluidAudio for speaker diarization in meeting mode. Here's what we learned shipping it on macOS 26. GitHub: github.com/Marvinngg/ambient-voice Architecture The app has two modes: hotkey dictation (press to talk, release to inject) and meeting recording (continuous transcription with a floating panel). Dictation Mode Audio capture uses AVCaptureSession (more on why below). The captured audio feeds into SpeechAnalyzer via an AsyncStream: let transcriber = SpeechTranscriber( locale: locale, transcriptionOptions: [], reportingOptions: [.volatileResults, .alternativeTranscriptions], attributeOptions: [.audioTimeRange, .transcriptionConfidence] ) let analyzer = SpeechAnalyzer(modules: [transcriber]) let (inputSequence, inputBuilder) = AsyncStream.makeStream() try await analyzer.start(inputSequence: inputSequence) While recording, we capture a screenshot of the focused window using ScreenCaptureKit, run Vision OCR (VNRecognizeTextRequest), extract keywords, and inject them into SpeechAnalyzer as contextual bias: let context = AnalysisContext() context.contextualStrings[.general] = ocrKeywords try await analyzer.setContext(context) This improves accuracy for technical terms and proper nouns visible on screen. If your screen shows "SpeechAnalyzer", saying it out loud is more likely to be transcribed correctly. After transcription, an optional L2 step sends the text through a local LLM (ollama) for spoken-to-written cleanup, then CGEvent simulates Cmd+V to paste into the active app. Meeting Mode Meeting mode forks the same audio stream to two consumers: SpeechAnalyzer — real-time streaming transcription, displayed in a floating NSPanel FluidAudio buffer — accumulates 16kHz Float32 mono samples for batch speaker diarization after recording stops When the user ends the meeting, FluidAudio's performCompleteDiarization() runs on the accumulated audio. We align transcription segments with speaker segments using audioTimeRange overlap matching — each transcription segment gets assigned the speaker ID with the most time overlap. Results export to Markdown. Pitfalls We Hit on macOS 26 1. AVAudioEngine installTap doesn't fire with Bluetooth devices We started with AVAudioEngine.inputNode.installTap() for audio capture. It worked fine with built-in mics but the tap callback never fired with Bluetooth devices (tested with vivo TWS 4 Hi-Fi). Fix: switched to AVCaptureSession. The delegate callback captureOutput(_:didOutput:from:) fires reliably regardless of audio device. The tradeoff is you get CMSampleBuffer instead of AVAudioPCMBuffer, so you need a conversion step. 2. NSEvent addGlobalMonitorForEvents crashes Our global hotkey listener used NSEvent.addGlobalMonitorForEvents. On macOS 26, this crashes with a Bus error inside GlobalObserverHandler — appears to be a Swift actor runtime issue. Fix: switched to CGEventTap. Works reliably, but the callback runs on a CFRunLoop context, which Swift doesn't recognize as MainActor. 3. CGEventTap callbacks aren't on MainActor If your CGEventTap callback touches any @MainActor state, you'll get concurrency violations. The callback runs on whatever thread owns the CFRunLoop. Fix: bridge with DispatchQueue.main.async {} inside the tap callback before touching any MainActor state. 4. CGPreflightScreenCaptureAccess doesn't request permission We used CGPreflightScreenCaptureAccess() as a guard before calling ScreenCaptureKit. If it returned false, we'd bail out. The problem: this function only checks — it never triggers macOS to add your app to the Screen Recording permission list. Chicken-and-egg: you can't get permission because you never ask for it. Fix: call CGRequestScreenCaptureAccess() at app startup. This adds your app to System Settings → Screen Recording. Then let ScreenCaptureKit calls proceed without the preflight guard — SCShareableContent will also trigger the permission prompt on first use. 5. Ad-hoc signing breaks TCC permissions on every rebuild During development, codesign --sign - (ad-hoc) generates a different code directory hash on every build. macOS TCC tracks permissions by this hash, so every rebuild = new app identity = all permissions reset. Fix: sign with a stable certificate. If you have an Apple Development certificate, use that. The TeamIdentifier stays constant across rebuilds, so TCC permissions persist. We also discovered that launching via open WE.app (LaunchServices) instead of directly executing the binary is required — otherwise macOS attributes TCC permissions to Terminal, not your app. Benchmarks We ran end-to-end benchmarks on public datasets (Mac Mini M4 16GB, macOS 26): Transcription (SpeechAnalyzer, AliMeeting Chinese): • Near-field CER 34% (excluding outliers ~25%) • Far-field CER 40% (single channel, no beamforming, >30% overlap) • Processing speed 74-89x real-time Speaker diarization (FluidAudio offline): • AMI English 16 meetings: avg DER 23.2% (collar=0.25s, ignoreOverlap=True) • AliMeeting Chinese 8 meetings: DER 48.5% (including overlap regions) • Memory: RSS ~500MB, peak 730-930MB Full evaluation methodology, scripts, and raw results are in the repo. Open Source The project is MIT licensed: github.com/Marvinngg/ambient-voice It includes the macOS client (Swift 6.2, SPM), server-side distillation/training scripts (Python), and a complete evaluation framework with reproducible benchmarks. Feedback and contributions welcome.
Replies
0
Boosts
0
Views
1.4k
Activity
Mar ’26
ScreenCaptureKit permissions lost after every build — solved by switching signing identity
Sharing a solution for a problem that took me a while to figure out. Problem: During development of a macOS 26 app that uses ScreenCaptureKit, the screen capture permissions were being reset after every build. Each time I compiled and ran the app from Xcode, I had to re-authorize screen capture in System Settings. CGPreflightScreenCaptureAccess() would return false even though I'd just granted permission minutes ago. Root cause: I was using ad-hoc code signing during development. macOS ties screen capture permissions to the app's code signing identity. With ad-hoc signing, the identity changes on every build, so the system treats each build as a "new" app. Solution: Switch to an Apple Development certificate for debug builds. In Xcode: Build Settings → Code Signing Identity → Debug → set to "Apple Development" Make sure your development team is selected After this change, the signing identity remains stable across builds, and screen capture permissions persist. This might be related to the broader issue discussed in this forum about ScreenCapture permissions disappearing — if other developers are seeing permissions vanish, it's worth checking whether the code signing identity is changing between sessions.
Replies
1
Boosts
0
Views
1.6k
Activity
Mar ’26
ScreenCapture permissions disappear and don't return
On Tahoe and earlier, ScreenCapture permissions can disappear and not return. Customers are having an issue with this disappearing and when our code executes CGRequestScreenCaptureAccess() nothing happens, the prompt does not appear. I can reproduce this by using the "-" button and removing the entry in the settings, then adding it back with the "+" button. CGPreflightScreenCaptureAccess() always returns the correct value but once the entry has been removed, CGRequestScreenCaptureAccess() requires a reboot before it will work again.
Replies
3
Boosts
0
Views
476
Activity
Mar ’26