Repository navigation
Fix broadcast screen share hanging without frames - #1142
Merged
Merged
Conversation
pblazej
requested review from
hiroshihorie and
xianshijing-lk
as code owners
September 30, 2026 06:34
- Check for a room and publisher before permissions in _publish; a disconnected room resets the local participant to default permissions, so publishing while disconnected reported a permission error - Stop the broadcast when the automatic screen share publish fails, instead of leaving the extension retrying against a stale socket - Skip the automatic publish in a disconnected room, so a stale Room cannot stop another room's working broadcast - Treat NWListener .waiting as transient rather than fatal - Fix the IPC reconnect delay, which UInt64(0.1) truncated to zero - Copy the shared NWParameters before setting requiredLocalEndpoint, so concurrent listeners stop binding each other's socket paths - Describe every LiveKitErrorType case; insufficientPermissions and deviceAccessDenied printed as "Unknown" Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pblazej
force-pushed
the
blaze/broadcast-fixes
branch
from
September 30, 2026 06:42
0a1fcf1 to
bbf2f5a
Compare
Two broadcast screen share capturers in one process (e.g. two connected rooms) listen on the same socket path, and only one of them ever gets the extension's connection; the other published a track with no frames and logged nothing. Warn when a second capturer starts. The capturer's receiver task waited for that connection forever and held the capturer strongly: stopCapture only closed a receiver that was never set, so every capture stopped before the extension connected leaked the capturer and its NWListener. Keep the task, cancel it on stop, and close the receiver from its cancellation handler. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A receiver task cancelled by stopCapture no longer calls stopCapture itself when it unwinds; after a quick restart that call stopped the new capture and cancelled its receiver - Count a capturer as active only while it has a receiver task, so a start without a socket path no longer leaves the count inflated and triggers a false multiple-share warning Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The receiver task could fail and call stopCapture before createReceiver stored it, so stop found no task to uncount and createReceiver then counted a capturer that had already stopped. Create, store and count the task under the receiver-task lock, which stopCapture also takes before uncounting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| @@ -49,17 +52,26 @@ class BroadcastScreenCapturer: BufferCapturer, @unchecked Sendable { | |||
| } | |||
|
|
|||
| private func createReceiver() -> Bool { | |||
Contributor
There was a problem hiding this comment.
can createReceiver() be called more than once ?
any chance that it will overwrite existing receiverTask without cancelling it or decrementing activeCount ?
| log("Broadcast receiver connected", .debug) | ||
| self.receiver = receiver | ||
| let isAnotherActive = receiverTask.mutate { receiverTask in | ||
| receiverTask = Task { [weak self] in await self?.receive(from: socketPath) }.cancellable() |
Contributor
There was a problem hiding this comment.
should activeCount track the task lifetime ? rather than task creation ?
If it is for task lifetime, I wonder if we should do
receiverTask = Task { [weak self] in
Self.activeCount.mutate { $0 += 1 }
defer {
Self.activeCount.mutate { $0 -= 1 }
}
await self?.receive(from: socketPath)
}.cancellable()
pblazej
added this pull request to stack #1146
October 6, 2026 09:08
hiroshihorie
approved these changes
Oct 6, 2026
hiroshihorie
added a commit
that referenced
this pull request
Oct 6, 2026
…the picker (#1145) Stacked on #1142. On iOS 27 with default options, a broadcast started from Control Center never reaches the broadcast extension path. `broadcastStateChanged` calls `setScreenShare(enabled: true)`, which takes the ScreenCaptureKit branch first because `useScreenCaptureKit` defaults to `true`. What happens next depends on the app state when the broadcast starts: - App active (Control Center closed during the countdown): the in-app SCK picker shows up, and Cancel or the 60s timeout fails the publish. - App inactive (Control Center still open): no picker shows, and the system cancels it about 2s later. On main the failure is only logged, so the broadcast keeps running and nothing gets published. With #1142 the failure calls `requestStop()`, so the broadcast ends. This change makes the publish triggered by a broadcast start use Broadcast Capture when the room is set up for the extension. Explicit `setScreenShare` calls keep using ScreenCaptureKit, so a stale `isBroadcasting` (for example after the extension is killed without posting stopped) can't turn them into a track that never receives frames. Verified on an iPhone 17 Pro Max on iOS 27.0.1 with the example app and its extension, in both Control Center cases: the `BroadcastScreenCapturer` track is published, the receiver connects, frames are sent, and there is no picker and no `requestStop`. Unit tests cover the routing helper, since the SCK branch is compiled out on the simulator.
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.
When the automatic screen share publish failed, the broadcast extension kept retrying against a socket with no listener, so the broadcast came up without frames.
_publishchecked permissions before the publisher, and a disconnected room resets the local participant's permissions, so it failed asUnknown(Participant does not have permission to publish); it now checks the publisher first, stops the broadcast on failure, and everyLiveKitErrorTypehas a description..waitingas transient, reconnects every 100 ms instead of busy-looping (UInt64(0.1)truncated the delay to zero), and copies the sharedNWParametersso concurrent listeners stop binding each other's paths, which also de-flakesIPCChannelTests.Network.framework is not the cause: the
SO_NECP_LISTENUUIDwarnings also appear in successful runs.Fixes: #1132