The Problem
When one participant stops sharing their screen, the other participant sees a black screen in the main video area. However, if the affected participant then clicks the PIP thumbnail to swap views, the remote video appears correctly.
This strongly suggests the issue is not a video stream problem (the stream is active) but rather an attachment/target timing issue in the active-share-change handler.
Our Analysis
The active-share-change Inactive Handler
When screen sharing ends, the Zoom Video SDK fires active-share-change with state: “Inactive” on both participants. Our handler (in VideoCall.jsx ) does the following:
// Re-attach both feeds to whichever layout is currently pinned
const nextPinnedView = pinnedViewRef.current;
const allUsers = client.getAllUser();
const myId = client.getCurrentUserInfo().userId;
const remoteUser = allUsers.find((u) => u.userId !== myId);
await new Promise((resolve) =>
requestAnimationFrame(() => requestAnimationFrame(resolve)),
);
// Re-attach local video
const localTargetSelector = nextPinnedView === "local"
? "#local-video-player"
: "#local-video-pip-player";
const localTarget = await waitForVideoPlayerReady(localTargetSelector, 3000);
if (localTarget) {
try { await stream.detachVideo(myId); } catch (_) {}
try {
await stream.attachVideo(
myId, VideoQuality.Video_720P, localTarget,
);
} catch (e) {
console.warn("[VideoCall] Failed to re-attach local video after share ended:", e);
}
}
// Re-attach remote video
if (remoteUser) {
const remoteTargetSelector = nextPinnedView === "local"
? "#remote-video-thumb-player"
: "#remote-video-player";
const remoteTarget = await waitForVideoPlayerReady(remoteTargetSelector, 3000);
try { await stream.detachVideo(remoteUser.userId); } catch (_) {}
if (remoteUser.bVideoOn && remoteTarget) {
try {
await stream.attachVideo(
remoteUser.userId, VideoQuality.Video_720P, remoteTarget,
);
} catch (e) {
console.warn("[VideoCall] Failed to re-attach remote video after share ended:", e);
}
}
}
}
Suspected Root Cause #1: bVideoOn Stale State
When screen sharing stops, the remote sharer’s bVideoOn property (checked via client.getAllUser()) may briefly be false during the SDK’s internal transition from share mode back to camera mode. Our guard:
// javascript
if (remoteUser.bVideoOn && remoteTarget) {
prevents the attachVideo call if bVideoOn is false at that moment. The video is never re-attached to the main player, leaving a black screen.
The user then manually swaps the view (triggers handlePinnedViewChange), which re-reads bVideoOn — by then it’s true — and attaches successfully.
This is our leading hypothesis. The bVideoOn check was intended to avoid attaching a null video, but it creates a race where the brief window between share-end and camera-restore causes a permanent black screen.
Suspected Root Cause #2: stopShareView() Timing
stopShareView() is called before re-attaching video feeds. If the SDK hasn’t fully cleaned up the share view before attachVideo runs, the attachment may silently fail or target a transitional state.
Suspected Root Cause #3: React CSS Transition Race
The main video container has a CSS transition:
// jsx
className={absolute inset-0 transition-all duration-300 ${ callStarted && !(remoteIsSharing || isScreenSharing) && pinnedView === "remote" ? "opacity-100" : "pointer-events-none opacity-0" }}
When setRemoteIsSharing(false) triggers a re-render immediately inside the Inactive handler, the container’s opacity transitions from 0 to 1 over 300ms. The attachVideo call runs before this transition completes — the element exists but is transitioning. While the SDK should render regardless of CSS opacity, this timing could compound with issues #1 or #2.
Questions
- Is there a documented state transition for bVideoOn when screen sharing stops? Specifically, does getAllUser().bVideoOn briefly return false between “sharing stopped” and “camera resumed”?
- Should stopShareView() be awaited before calling attachVideo() / detachVideo()? Is there a required cooldown or lifecycle step after stopShareView() before video streams can be re-established?
- Can you suggest the recommended pattern for re-attaching video feeds after screen sharing ends? Specifically:
- Should we use a peer-video-state-change event with action: “Start” as a trigger to re-attach, rather than doing it immediately in the active-share-change Inactive handler?
- Is there an event that fires specifically when the sharer’s camera feed is fully restored after sharing ends?
- What is the expected value of bVideoOn for a participant who was sharing and has an active camera? Does bVideoOn remain true during sharing (since camera is still active) or does it toggle off temporarily?
Relevant Code for Context
The DOM layout on the receiver’s side when screen sharing is active (from VideoCallInCall.jsx):
{/ Remote video — hidden during share, shown when share ends /}
{/ Always-present canvas for remote share view /}
<canvas id="screen-share-view-canvas" className={ absolute inset-0 h-full w-full ${
remoteIsSharing ? "" : "pointer-events-none opacity-0"
}} />