Skip to content

fix(macos): resolve ffmpeg export memory crashes and macOS desktopCapturer permission blocks#474

Open
designedbyalok wants to merge 1 commit intosiddharthvaddem:mainfrom
designedbyalok:fix-macos-fallback-ui
Open

fix(macos): resolve ffmpeg export memory crashes and macOS desktopCapturer permission blocks#474
designedbyalok wants to merge 1 commit intosiddharthvaddem:mainfrom
designedbyalok:fix-macos-fallback-ui

Conversation

@designedbyalok
Copy link
Copy Markdown

@designedbyalok designedbyalok commented Apr 19, 2026

Pull Request Template

Description

This PR comprehensively resolves a chain of critical bugs impacting the core video rendering engine and macOS screen capture capabilities.

  1. The Core Export Fix: Finalizes the transition away from heavy, unstable ffmpeg-static child processes. By clearing out the stale dependency tree and updating the lockfile, video frames are now strictly rendered and packaged into MP4s natively within the browser sandbox using HTML5 WebCodecs and mediabunny. This completely eliminates the Cannot allocate memory (ENOMEM) crashes caused by ffmpeg spawning massive memory buffers during export.
  2. The Source Selector Fix: Fixes local execution pipelines where the newly built export application would fail to fetch screens if local macOS developer certificates were ambiguously configured or universally revoked.
  3. The UX Fail-Safe: Added a custom fallback UI inside SourceSelector.tsx to beautifully accommodate and explain macOS's hidden permission lockouts. If macOS silently blocks the desktopCapturer permissions due to code-signing changes, the UI correctly instructs the user exactly how to restore the permission instead of trapping them in an ambiguous 'blank box' screen.

Motivation

Previously, video exports would crash on long buffers due to internal ffmpeg memory saturation. When trying to fix this via a fresh install targeting the new WebCodecs pipeline, macOS would aggressively and silently block the newly built app's Screen Recording permissions due to underlying invalid binary hashes and revoked code-signing identities. This created a frustrating dual-failure state where the user couldn't export videos reliably, and local debug builds were met with a blank screen-capture window. This PR restores stable, low-memory exports and introduces a highly visible user experience when macOS's OS-level security silently restricts the capture pipeline.

Type of Change

  • New Feature
  • Bug Fix
  • Refactor / Code Cleanup
  • Documentation Update
  • Other (please specify)

Related Issue(s)

Screenshots / Video

Screenshot (if applicable):

N/A

Video (if applicable):

N/A

Testing

  1. Test the video export pipeline with a long recording to ensure WebCodecs safely renders the .mp4 utilizing native Chromium browser memory without throwing Cannot allocate memory Node.js pipeline failures.
  2. Purposely run an ad-hoc or incorrectly signed macOS build to force macOS to naturally invalidate the previous Privacy & Security > Screen Recording OS configuration.
  3. Launch the application and click to select a screen to record. Verify that SourceSelector.tsx properly catches the empty screen array and renders the new "Screen access blocked by macOS" red fallback warning prompt.

Checklist

  • I have performed a self-review of my code.
  • I have added any necessary screenshots or videos.
  • I have linked related issue(s) and updated the changelog if applicable.

Summary by CodeRabbit

  • New Features
    • Added an empty-state message in the screens tab that displays when screen access is blocked by macOS, instructing users to adjust Screen Recording permissions.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 19, 2026

📝 Walkthrough

Walkthrough

Added a conditional empty-state message to the screens tab in SourceSelector. When no screen sources are available and not loading, users now see a centered notification explaining that macOS blocked screen access and need to adjust Screen Recording permissions. the grid renders only when sources are present.

Changes

Cohort / File(s) Summary
Empty State UI
src/components/launch/SourceSelector.tsx
Added conditional empty-state render block in screens tab to display macOS permission access message when screenSources.length === 0 and not loading. Grid renders unaffected after this state.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Suggested reviewers

  • siddharthvaddem

Poem

🖥️ screens were blocked by the mac gods above,
now users will know what they're dreaming of—
a little message, six lines of care,
saying "check permissions, they're hanging in air" ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning Title mentions ffmpeg crashes and macOS permission blocks, but the actual changeset only adds a UI fallback for permission blocks—no ffmpeg changes are present in the modified files. Align title with actual changes: 'fix(macos): add SourceSelector fallback UI for blocked screen recording permissions' or split into separate PRs.
Description check ⚠️ Warning Description is detailed and well-structured, covering motivation and testing guidance. However, it claims to fix ffmpeg export crashes and dependency updates that don't appear in the changeset—only the SourceSelector UI fallback was modified. Remove claims about ffmpeg fixes and dependency updates; focus description on the SourceSelector fallback UI changes only.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/components/launch/SourceSelector.tsx (1)

130-130: nit: !loading is always true here

the component early-returns the spinner at line 61-73 when loading is truthy, so by the time this JSX renders loading is guaranteed false. the && !loading guard is dead weight — safe to drop for a cleaner condition. not a bug, just tidy-up fuel.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/launch/SourceSelector.tsx` at line 130, The JSX condition in
SourceSelector.tsx currently checks {screenSources.length === 0 && !loading &&
(...)} but loading is already gated by the early return spinner in the component
(spinner returned in the loading block around lines 61-73), so remove the
redundant && !loading check and simplify the condition to just
screenSources.length === 0 to make the code cleaner (update the conditional
expression where the empty-state render is performed).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/launch/SourceSelector.tsx`:
- Around line 130-135: The fallback message for an empty screenSources (in the
JSX branch that checks screenSources.length === 0 && !loading) is hardcoded and
macOS-specific; update it to use the component's i18n hooks (useScopedT / t)
with new keys like sourceSelector.screenBlocked.title and
sourceSelector.screenBlocked.hint, and make the wording platform-neutral by
default, only appending a macOS-specific hint when a platform check indicates
macOS (use window.electronAPI.platform or navigator.platform). Also replace HTML
entities with plain characters for the settings path. Locate the conditional
that renders when screenSources.length === 0 && !loading and swap the hardcoded
strings for t(...) lookups and conditional platform logic.

---

Nitpick comments:
In `@src/components/launch/SourceSelector.tsx`:
- Line 130: The JSX condition in SourceSelector.tsx currently checks
{screenSources.length === 0 && !loading && (...)} but loading is already gated
by the early return spinner in the component (spinner returned in the loading
block around lines 61-73), so remove the redundant && !loading check and
simplify the condition to just screenSources.length === 0 to make the code
cleaner (update the conditional expression where the empty-state render is
performed).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7958e4c0-fe48-45c7-aaad-17c97a69324d

📥 Commits

Reviewing files that changed from the base of the PR and between 2b1c931 and 943c290.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (1)
  • src/components/launch/SourceSelector.tsx

Comment on lines +130 to +135
{screenSources.length === 0 && !loading && (
<div className="flex flex-col items-center justify-center h-[280px] text-center px-4">
<p className="text-sm text-red-400 mb-2 font-medium">Screen access blocked by macOS</p>
<p className="text-xs text-zinc-400">Please check Screen Recording in System Settings &gt; Privacy &amp; Security. If checked, remove it with (-) and add it again.</p>
</div>
)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

hardcoded strings bypass i18n + message is platform-assuming

two things worth tightening here:

  1. the rest of this component routes every user-facing string through useScopedT (t("sourceSelector.*"), tc("actions.*")), but this new fallback hardcodes english copy. users on non-english locales will get a jarring mix. worth adding keys like sourceSelector.screenBlocked.title / sourceSelector.screenBlocked.hint and pulling them through t().

  2. the copy asserts "Screen access blocked by macOS", but this branch fires on any empty screenSources result — linux/windows builds, transient electron hiccups, or even a genuinely screenless session would all show this mac-specific guidance. kinda cursed if someone hits this on windows. either gate the message behind a platform check (e.g. window.electronAPI exposing platform, or navigator.platform), or soften the copy to be platform-neutral with the mac-specific hint conditional.

also minor: raw &gt; / &amp; entities render fine but in JSX you can just write > and & directly (or use for the settings-path breadcrumb, which reads nicer).

♻️ sketch of the shape
-							{screenSources.length === 0 && !loading && (
+							{screenSources.length === 0 && (
 								<div className="flex flex-col items-center justify-center h-[280px] text-center px-4">
-									<p className="text-sm text-red-400 mb-2 font-medium">Screen access blocked by macOS</p>
-									<p className="text-xs text-zinc-400">Please check Screen Recording in System Settings &gt; Privacy &amp; Security. If checked, remove it with (-) and add it again.</p>
+									<p className="text-sm text-red-400 mb-2 font-medium">
+										{t("sourceSelector.screenBlocked.title")}
+									</p>
+									<p className="text-xs text-zinc-400">
+										{t("sourceSelector.screenBlocked.hint")}
+									</p>
 								</div>
 							)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/launch/SourceSelector.tsx` around lines 130 - 135, The
fallback message for an empty screenSources (in the JSX branch that checks
screenSources.length === 0 && !loading) is hardcoded and macOS-specific; update
it to use the component's i18n hooks (useScopedT / t) with new keys like
sourceSelector.screenBlocked.title and sourceSelector.screenBlocked.hint, and
make the wording platform-neutral by default, only appending a macOS-specific
hint when a platform check indicates macOS (use window.electronAPI.platform or
navigator.platform). Also replace HTML entities with plain characters for the
settings path. Locate the conditional that renders when screenSources.length ===
0 && !loading and swap the hardcoded strings for t(...) lookups and conditional
platform logic.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

openscreen/package-lock.json

Lines 5009 to 5012 in 943c290

"peerDependencies": {
"msw": "^2.4.9",
"vite": "^6.0.0 || ^7.0.0-0"
},

P1 Badge Restore Vitest nested vite entries in lockfile

This lockfile now keeps @vitest/mocker entries that peer on vite but drops the corresponding node_modules/@vitest/browser*/node_modules/vite package records, which makes clean installs fail. On a fresh checkout, npm ci --dry-run errors with Missing: vite@ from lock file, so CI/developer environments that use npm ci cannot install dependencies from this commit.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +130 to +133
{screenSources.length === 0 && !loading && (
<div className="flex flex-col items-center justify-center h-[280px] text-center px-4">
<p className="text-sm text-red-400 mb-2 font-medium">Screen access blocked by macOS</p>
<p className="text-xs text-zinc-400">Please check Screen Recording in System Settings &gt; Privacy &amp; Security. If checked, remove it with (-) and add it again.</p>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Surface macOS permission warning on the initial empty state

The new warning is rendered only inside the screens tab content, but this component defaults to the windows tab whenever screenSources.length === 0 (existing logic at defaultValue), so in the exact no-screen scenario the user first sees an empty grid and not the remediation message. That defeats the new fallback for the primary failure case (blocked screen capture permissions).

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant