From 5725d0b6bededba76bb257ffc618b5bf8b8eea9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C5=82az=CC=87ej=20Pankowski?= <86720177+pblazej@users.noreply.github.com> Date: Thu, 14 May 2026 10:56:04 +0200 Subject: [PATCH] Keep screen share alive across full reconnect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `republishAllTracks()` routes every local track through `unpublish()`, which calls `track.stop()` and tears down the capturer. For iOS broadcast extension and ReplayKit screen capture this kills the IPC and the extension self-terminates — the track gets re-added to the new publisher but no frames flow. Detach screen share publications from the old publisher peer connection without going through `unpublish()` so the capturer keeps running, then let `_publish` reattach it to the new publisher. Resolves #1004 Co-Authored-By: Claude Opus 4.7 (1M context) --- .changes/screen-share-survives-reconnect | 1 + .../LiveKit/Participant/LocalParticipant.swift | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 .changes/screen-share-survives-reconnect diff --git a/.changes/screen-share-survives-reconnect b/.changes/screen-share-survives-reconnect new file mode 100644 index 000000000..fcd245c7b --- /dev/null +++ b/.changes/screen-share-survives-reconnect @@ -0,0 +1 @@ +patch type="fixed" "Screen sharing no longer fails to resume after a full reconnect: the capture source (iOS broadcast extension IPC, ReplayKit) is kept alive while the track is reattached to the new publisher" diff --git a/Sources/LiveKit/Participant/LocalParticipant.swift b/Sources/LiveKit/Participant/LocalParticipant.swift index 146fda51b..06d17971d 100644 --- a/Sources/LiveKit/Participant/LocalParticipant.swift +++ b/Sources/LiveKit/Participant/LocalParticipant.swift @@ -304,6 +304,21 @@ extension LocalParticipant { func republishAllTracks() async throws { let mediaTracks = _state.trackPublications.values.map { $0.track as? LocalTrack }.compactMap(\.self) + // Detach screen share tracks without stopping their capturers — the + // underlying sources (iOS broadcast extension IPC, ReplayKit) cannot + // be restarted programmatically. + let screenShareTracks = mediaTracks.filter { $0.source == .screenShareVideo } + for track in screenShareTracks { + let sidsToRemove = _state.trackPublications.filter { $0.value.track === track }.map(\.key) + _state.mutate { + for sid in sidsToRemove { + $0.trackPublications.removeValue(forKey: sid) + } + } + await track.set(transport: nil, rtpSender: nil) + track._state.mutate { $0.rtpSenderForCodec.removeAll() } + } + await unpublishAll() for mediaTrack in mediaTracks {