Fix hole-punch: put native video BEHIND the WebView, not under its scrollView
Diagnosis: no native video rendered at all (bg went black, but camera AND screen tiles were empty). WKWebView does NOT composite native subviews placed under its scrollView through transparent web content — only the webView's own layer background shows through. So the video was effectively invisible. Fix (the standard hole-punch): insert the video tiles into the WebView's SUPERVIEW, BEHIND the (fully transparent) WebView, so the web content paints on top and the video shows through. Changes: - makeTileView / syncVideoTiles: insert belowSubview: host (the WebView) in host.superview, and offset frames by the WebView's origin (getBoundingClientRect is viewport-relative). - webView.backgroundColor = .clear (was .black — a black webView bg would have occluded the video behind it); the VC view's black background is the backing. Plugin-only change — needs a Codemagic build. Web (transparent chain, zoom, bz-hasvid) already correct. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -178,11 +178,12 @@ public class NativeCallPlugin: CAPPlugin, CAPBridgedPlugin, PKPushRegistryDelega
|
||||
}
|
||||
}
|
||||
|
||||
// Create a tile view and place it BEHIND the web content (hole-punch), so the web tile's own name/mute/
|
||||
// border/avatar render on top. No native overlays needed — the WebView draws them.
|
||||
// Create a tile view BEHIND the whole WebView (in its superview). WKWebView does NOT composite native
|
||||
// subviews placed under its scrollView through transparent web content, so the video must sit behind the
|
||||
// (transparent) WebView itself; the web UI then paints on top. No native overlays — the web tile draws them.
|
||||
private func makeTileView(host: WKWebView, key: String) -> TileVideoView {
|
||||
let v = TileVideoView(frame: .zero)
|
||||
host.insertSubview(v, belowSubview: host.scrollView)
|
||||
if let sup = host.superview { sup.insertSubview(v, belowSubview: host) } else { host.addSubview(v) }
|
||||
tileViews[key] = v
|
||||
return v
|
||||
}
|
||||
@@ -201,9 +202,9 @@ public class NativeCallPlugin: CAPPlugin, CAPBridgedPlugin, PKPushRegistryDelega
|
||||
self.savedVCBg = self.bridge?.viewController?.view.backgroundColor
|
||||
}
|
||||
web.isOpaque = false
|
||||
web.backgroundColor = .black // shows through the transparent meeting; also the letterbox bg
|
||||
web.backgroundColor = .clear // fully transparent so the video BEHIND the WebView shows through
|
||||
web.scrollView.backgroundColor = .clear
|
||||
self.bridge?.viewController?.view.backgroundColor = .black // backing so the transparent meeting reads black, not white
|
||||
self.bridge?.viewController?.view.backgroundColor = .black // the black backing (behind the video) for gaps/letterbox
|
||||
self.holePunchOn = true
|
||||
} else {
|
||||
self.applyHolePunchRestore(web)
|
||||
@@ -388,12 +389,13 @@ public class NativeCallPlugin: CAPPlugin, CAPBridgedPlugin, PKPushRegistryDelega
|
||||
: 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.insertSubview(vv, belowSubview: host.scrollView) } // keep BEHIND web UI
|
||||
if vv.superview !== host.superview, let sup = host.superview { sup.insertSubview(vv, belowSubview: host) } // keep BEHIND the transparent WebView
|
||||
vv.layoutMode = wantScreen ? .fit : .fill
|
||||
if vv.track !== track { vv.track = track }
|
||||
// The container tracks the tile rect; the zoom transform (web-forwarded via setTileZoom) lives on
|
||||
// the INNER video, so this never fights an active zoom.
|
||||
vv.frame = CGRect(x: x, y: y, width: w, height: h)
|
||||
// The container tracks the tile rect (getBoundingClientRect is in the WebView's viewport, so map
|
||||
// it into the superview by adding the WebView's origin). The zoom transform (web-forwarded via
|
||||
// setTileZoom) lives on the INNER video, so this never fights an active zoom.
|
||||
vv.frame = CGRect(x: x + host.frame.minX, y: y + host.frame.minY, width: w, height: h)
|
||||
}
|
||||
// Drop views for participants no longer present / camera turned off.
|
||||
for (key, vv) in self.tileViews where !wanted.contains(key) {
|
||||
|
||||
Reference in New Issue
Block a user