diff --git a/mobile/plugins/native-call/ios/Sources/NativeCallPlugin/NativeCallPlugin.swift b/mobile/plugins/native-call/ios/Sources/NativeCallPlugin/NativeCallPlugin.swift index 8bd80e6..38ae723 100644 --- a/mobile/plugins/native-call/ios/Sources/NativeCallPlugin/NativeCallPlugin.swift +++ b/mobile/plugins/native-call/ios/Sources/NativeCallPlugin/NativeCallPlugin.swift @@ -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. diff --git a/server/public/home.html b/server/public/home.html index 467cefa..6a8119b 100644 --- a/server/public/home.html +++ b/server/public/home.html @@ -5221,6 +5221,7 @@ function meetMakePeer(peerId, name){ // (no extra tiles, the peer's video just shows the screen). Falls back to addTrack+renegotiate if // no video sender exists yet (camera never turned on). Stopping restores the camera (or avatar). async function toggleScreen(){ + if(meetNative){ toast('Sharing your screen from iPhone isn’t supported yet — but you’ll see others’ shared screens.'); return; } // iOS has no getDisplayMedia / ReplayKit ext wired if(meetScreen){ stopScreen(); return; } if(!meetMultiShare && meetSharers.size>0){ toast('Someone is already sharing their screen'); return; } if(SFU.on){ @@ -5378,11 +5379,11 @@ function bzNativeSyncTiles(){ document.querySelectorAll('#meetGrid .meet-tile').forEach(el=>{ const id=el.id ? el.id.replace('meet-tile-','') : ''; if(!id || id==='__waiting') return; const r=el.getBoundingClientRect(); if(r.width<2||r.height<2) return; - let uid, local=false, name='', muted=false; + let uid, local=false, name='', muted=false, screen=false; if(id==='__local'){ uid=(ME&&ME.id)||''; local=true; name=(ME&&ME.name)||'You'; muted=!meetMic; } - else { uid=meetPeerUids.get(id)||''; name=meetNames.get(id)||'Guest'; muted=!!meetMuted.get(id); } + else { uid=meetPeerUids.get(id)||''; name=meetNames.get(id)||'Guest'; muted=!!meetMuted.get(id); screen=meetSharers.has(id); } // sharer's tile → render their screen, not camera if(!uid) return; - tiles.push({ uid, local, name, muted, x:r.left, y:r.top, w:r.width, h:r.height }); + tiles.push({ uid, local, name, muted, screen, x:r.left, y:r.top, w:r.width, h:r.height }); }); try{ NC.syncVideoTiles({ tiles }); }catch(_){} }