Skip to content

macOS screen capture still freezes on the first frame while the cursor moves (0.17.0) #344

Open

Description

@nisfeb

Describe the bug

The frozen screen from #80 is still happening for us on 0.17.0. We ship a desktop app built on webrtc-java, and one of our macOS users shared their whole screen with VideoDesktopSource (a ScreenCapturer source). Everyone else saw the first frame of their screen and nothing after that. Their mouse cursor kept moving around on top of it, but the windows underneath stayed frozen, including an animation that was stuck halfway through. Sharing a single window (WindowCapturer source) didn't work for them at all.

That's the same thing luokeith described in #80 in June 2024 on macOS 14.2.1 ("the screen displays only the content of the first frame, but the cursor is movable"). It looks like d1f1035 didn't fully fix it.

To Reproduce

  1. On macOS, list sources with ScreenCapturer and pick a screen.
  2. VideoDesktopSource, setSourceId(id, false), start(), and send the track to a peer.
  3. Move some windows around on the shared screen. The far side keeps showing the first frame, with only the cursor moving.

Expected behavior

The far side sees the screen as it changes.

Desktop

  • OS: macOS (we're still getting the exact version from the user and will add it here)
  • webrtc-java: 0.17.0

Additional context

We don't have a Mac on hand, so this comes from reading the 0.17.0 source and libwebrtc at branch-heads/7977, not from a debugger. Here's what we think is going on:

  • DesktopCaptureOptions::allow_sck_capturer() defaults to false, and VideoTrackDesktopSource::capture() never turns it on. So screen_capturer_darwin.mm never tries the ScreenCaptureKit capturer, and you always get ScreenCapturerMac, the old CGDisplayStream one.
  • ScreenCapturerMac only gets new pixels when its CGDisplayStream handler fires. RegisterRefreshAndMoveHandlers adds that stream's source to CFRunLoopGetCurrent(), the capture thread's run loop, which the capture loop pumps with CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) once per frame. If the handler stops firing, CaptureFrame() keeps copying the previous frame and still reports SUCCESS.
  • DesktopAndCursorComposer draws the cursor fresh on every frame. So a stale frame with a live cursor is exactly what you'd expect to see, and frames keep flowing, so nothing on our side notices.
  • Window capture goes through WindowCapturerMac → DesktopFrameCGImage::CreateForWindow → CGWindowListCreateImage, which Apple deprecated in favour of ScreenCaptureKit. When it returns nothing, the capture is just ERROR_TEMPORARY every frame, so no frames go out at all.

What we'd suggest is setting options.set_allow_sck_capturer(true) on macOS in VideoTrackDesktopSource::capture(), and probably in DesktopCapturer for source listing too. libwebrtc already falls back to ScreenCapturerMac where ScreenCaptureKit isn't available. For windows it might be worth looking at allow_sck_system_picker as well, so macOS's own picker handles screens and windows the way the Wayland portal does now.

Chromium uses ScreenCaptureKit for its own screen sharing on recent macOS, so that seems like the right direction. We're happy to test a build, or to send a PR if that helps.

Activity

  1. devopvoid commented on Oct 11, 2026

    @devopvoid
    Owner

    Hi, thanks for reporting the issue and for your suggestions. I'll take a look at it over the next few days. I have an Apple Silicon M2 device available for testing.

  2. devopvoid commented on Oct 11, 2026

    @devopvoid
    Owner

    Thanks again for the detailed analysis. It matches what I found in WebRTC: until its CGDisplayStream calls back, ScreenCapturerMac uses a snapshot of the whole screen. After that it only copies the areas the stream reports as changed. So if the stream never calls back, you get the first frame for good, with the cursor drawn live on top.

    I couldn't reproduce it on macOS 14.5 on an M2. The current capturer followed a window flashing in another process every 400 ms, and window capture worked too. So I'd like to know a bit more about your user's setup:

    • Which macOS version are they on?
    • Does your app have Screen Recording permission (System Settings → Privacy & Security → Screen & System Audio Recording)?

    #354 switches screen capture to ScreenCaptureKit on macOS 14 and newer, as you suggested, and keeps the old capturer on older releases. I've left source listing on the old capturer, because without the system picker ScreenCaptureKit returns an empty screen list. Window capture is unchanged for now, because this WebRTC branch has no ScreenCaptureKit window capturer outside the system picker. I'll look at the picker separately. A test build of #354 on your user's machine would help a lot.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions