fix: reject recording when the selected display is disconnected - #185
Merged
Merged
Conversation
A display picked from the content picker is captured by its `displayID`. Disconnecting the display leaves the filter intact, so recording starts normally but the stream never delivers a frame. With audio enabled the writer session is started by the first audio buffer, which satisfies the `noFramesWritten` guard, and the recording is finalized and reported as saved with audio tracks but no video track at all. Validate the selected display against the connected displays before creating the stream, and clear the stale selection so the next attempt opens the picker. Report the number of video frames from `finishWriting` so a recording that ends up without video is saved but flagged to the user instead of announced as a normal success. Closes jsattler#184
Owner
|
@krishna2206 thanks for contributing this fix. I'm going to have a look. In case there are no major blockers it should be available in the next release. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #184.
Recording against a display that was disconnected after being selected produces a file with audio tracks and no video track, reported to the user as a normal "Recording Saved". This makes that impossible in two independent places.
Changes
1. Validate the selected display before creating the stream
ContentFilterService.isSelectedDisplayConnected(_:)checks the filter'sdisplayIDagainst the currently connected displays.CaptureEngine.startCapturefails with a newCaptureError.selectedDisplayDisconnectedinstead of starting a stream that will never deliver frames.RecorderViewModel.startRecordingthen clears the stale selection, so pressing record again opens the picker rather than failing the same way. SincelastErrorhas no UI representation anywhere in the app, the reason is surfaced as a notification — otherwise the record button would simply do nothing.Two deliberate scoping decisions, both easy to change if you'd prefer otherwise:
filter.style == .display. Window and application filters aren't bound to a display, and theirincludedDisplayssnapshot could name a display the window has since moved off, which would block a capture that would actually work.SCShareableContentcan't be read, validation is skipped and capture proceeds. A validation step shouldn't become a new way to fail.2. Report the video frame count from
finishWritingThe
noFramesWrittenguard only checkshasStartedSession, andappendAudioSamplesets that flag too — so with audio enabled it never fires no matter how many video frames were dropped.finishWriting()now returns(url:videoFrameCount:), andstopRecordingsends a distinct "Recording Saved Without Video" notification when the count is zero.The file is deliberately kept, not cancelled:
cancel()deletes the output, and in the reported case that would have discarded nearly two hours of otherwise-valid audio. The notification uses the existing saved category so "Show in Finder" still works.This second change is the backstop — it catches any future cause of a video-less recording, not just a disconnected display.
Tests
New
AssetWriterTestscovering the frame-count reporting:audioOnlyRecordingReportsZeroVideoFrames— feeds only audio, asserts the count is zero, the file still exists, andAVURLAssetreports no video track. This reproduces the reported failure directly.recordingWithVideoReportsFramesWritten— positive control with synthesized frames markedSCFrameStatus.complete.recordingWithoutAnySampleThrows— confirms the existingnoFramesWrittenbehaviour is unchanged..selectedDisplayDisconnectedadded to theErrorTestsdescription coverage. Full suite: 96 tests passing on macOS 15.7.7 / Xcode 26.3.Note on verification: the display-validation path itself isn't unit tested, because
SCShareableContentrequires screen recording TCC, which the test runner doesn't have — it throws-3801there, which is exactly the skip-validation path. I verified the frame-count half end to end via the tests above and the validation half by inspection. Happy to add coverage if you have a preferred way to fakeSCShareableContentin this project.I have SwiftLint unavailable locally, so the lint run is on CI — let me know if anything trips and I'll fix it.
Not addressed
Disconnecting a display during an active recording is still unhandled — the video track just stops partway while audio continues. That needs display-reconfiguration observation, which felt like a separate change; happy to follow up if you want it.