Native video: render others' screen-share on native calls

Last native-video gap: on a native (iOS) call the WebView has no LiveKit
connection, so a screen shared by a web/desktop participant never rendered.
The mesh already flips the sharer's tile into the big "stage" (meeting-
peer-screen -> meetSharers -> sharing-mode), so extend the tile sync: each
tile now carries a `screen` flag (meetSharers.has(id)). The plugin renders
that participant's screen-share track (source .screenShareVideo) on the
tile with layoutMode .fit (contain, no crop) instead of the camera.

Outgoing screen-share from iOS is still unsupported (no getDisplayMedia /
ReplayKit broadcast extension) — toggleScreen now shows a clear toast on
native instead of silently failing.

Needs a Codemagic build (plugin change). Web deployed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 16:39:39 +05:30
parent a5e2ed8a9d
commit e6d3d2d66a
2 changed files with 18 additions and 4 deletions
@@ -140,6 +140,14 @@ public class NativeCallPlugin: CAPPlugin, CAPBridgedPlugin, PKPushRegistryDelega
return pub.track as? VideoTrack
}
// The remote screen-share track for a user id nil if they aren't sharing (or it isn't subscribed yet).
// (Local screen-share isn't supported on iOS no ReplayKit broadcast extension so this is remote-only.)
private func screenTrack(forUid uid: String) -> VideoTrack? {
guard let room = room, let p = room.remoteParticipants[Participant.Identity(from: uid)] else { return nil }
guard let pub = p.videoTracks.first(where: { $0.source == .screenShareVideo && !$0.isMuted && $0.track != nil }) else { return nil }
return pub.track as? VideoTrack
}
private func tileKey(uid: String, isLocal: Bool) -> String { isLocal ? "__local" : uid }
private func removeAllTileViews() {
@@ -337,10 +345,15 @@ public class NativeCallPlugin: CAPPlugin, CAPBridgedPlugin, PKPushRegistryDelega
let x = num("x"), y = num("y"), w = num("w"), h = num("h")
if w < 2 || h < 2 { continue }
let key = self.tileKey(uid: uid, isLocal: isLocal)
guard let track = self.cameraTrack(forUid: uid, isLocal: isLocal) else { continue } // camera off leave the web avatar
// A tile flagged `screen` is a sharer's stage tile show their screen-share track (fit, so it
// isn't cropped); otherwise the camera (fill). Either is nil when off web avatar shows.
let wantScreen = (t["screen"] as? Bool) ?? false
guard let track = wantScreen ? self.screenTrack(forUid: uid)
: self.cameraTrack(forUid: uid, isLocal: isLocal) else { continue }
wanted.insert(key)
let vv = self.tileViews[key] ?? self.makeTileView(host: host, key: key)
if vv.superview !== host { host.addSubview(vv) }
vv.layoutMode = wantScreen ? .fit : .fill
if vv.track !== track { vv.track = track }
vv.frame = CGRect(x: x, y: y, width: w, height: h)
// Native video covers the web tile, so redraw the essentials (name + muted) natively.