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:
@@ -140,6 +140,14 @@ public class NativeCallPlugin: CAPPlugin, CAPBridgedPlugin, PKPushRegistryDelega
|
|||||||
return pub.track as? VideoTrack
|
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 tileKey(uid: String, isLocal: Bool) -> String { isLocal ? "__local" : uid }
|
||||||
|
|
||||||
private func removeAllTileViews() {
|
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")
|
let x = num("x"), y = num("y"), w = num("w"), h = num("h")
|
||||||
if w < 2 || h < 2 { continue }
|
if w < 2 || h < 2 { continue }
|
||||||
let key = self.tileKey(uid: uid, isLocal: isLocal)
|
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)
|
wanted.insert(key)
|
||||||
let vv = self.tileViews[key] ?? self.makeTileView(host: host, key: key)
|
let vv = self.tileViews[key] ?? self.makeTileView(host: host, key: key)
|
||||||
if vv.superview !== host { host.addSubview(vv) }
|
if vv.superview !== host { host.addSubview(vv) }
|
||||||
|
vv.layoutMode = wantScreen ? .fit : .fill
|
||||||
if vv.track !== track { vv.track = track }
|
if vv.track !== track { vv.track = track }
|
||||||
vv.frame = CGRect(x: x, y: y, width: w, height: h)
|
vv.frame = CGRect(x: x, y: y, width: w, height: h)
|
||||||
// Native video covers the web tile, so redraw the essentials (name + muted) natively.
|
// Native video covers the web tile, so redraw the essentials (name + muted) natively.
|
||||||
|
|||||||
@@ -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 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).
|
// no video sender exists yet (camera never turned on). Stopping restores the camera (or avatar).
|
||||||
async function toggleScreen(){
|
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(meetScreen){ stopScreen(); return; }
|
||||||
if(!meetMultiShare && meetSharers.size>0){ toast('Someone is already sharing their screen'); return; }
|
if(!meetMultiShare && meetSharers.size>0){ toast('Someone is already sharing their screen'); return; }
|
||||||
if(SFU.on){
|
if(SFU.on){
|
||||||
@@ -5378,11 +5379,11 @@ function bzNativeSyncTiles(){
|
|||||||
document.querySelectorAll('#meetGrid .meet-tile').forEach(el=>{
|
document.querySelectorAll('#meetGrid .meet-tile').forEach(el=>{
|
||||||
const id=el.id ? el.id.replace('meet-tile-','') : ''; if(!id || id==='__waiting') return;
|
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;
|
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; }
|
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;
|
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(_){}
|
try{ NC.syncVideoTiles({ tiles }); }catch(_){}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user