Screen Sharing
Screen sharing is a method on the SelfParticipant. Call
startScreenShare() to add a screen-share track to the active call;
call stopScreenShare() to remove it. The SDK calls
getDisplayMedia() under the hood, negotiates the additional track,
and pushes status changes through an observable.
There’s no separate “screen share call” — the share is added to the existing call alongside the camera, as a second video stream.
You’ll need an active call. Screen share attaches to an existing
Call — start one of these first.
Install the SDK and create a SignalWire client with a credential provider.
Dial a destination with client.dial() to get a Call instance.
Subscribe to client.session.incomingCalls$ and call.answer().
Start / stop
startScreenShare() triggers the browser’s screen-picker. The
promise rejects on cancel and on permission denial — you need to
tell them apart to avoid showing a scary error toast for what was a
deliberate user choice.
Handling permission and cancel
The browser surfaces both outcomes as a DOMException from
getDisplayMedia(). Use err.name to distinguish:
NotAllowedError is the one to watch — its message contains
"Permission denied" when the OS or browser blocked the prompt, vs
"Permission denied by user" (Chromium) or an empty message
(Firefox / Safari) when the user clicked Cancel. The distinction is
fuzzy across browsers, so the safest UX is: silent on
NotAllowedError and rely on the user re-trying, since they just
made an explicit choice.
Detect support before showing the button
getDisplayMedia() is unavailable on iOS Safari and some embedded
WebViews. Gate the button on the capability and the API existence
so the user never sees a control that can’t work:
Observing share state
screenShareStatus$ is the reactive form. Use it instead of polling
screenShareStatus so your UI reflects auto-stop (user clicked
“Stop sharing” in the browser bar, OS revoked permission, etc.):
How the share appears to other participants
The screen-share track is delivered as a separate participant entry
in call.participants$ — usually with a name like Screen or the
sharer’s name suffixed with (Screen). The local participant
doesn’t need to render their own share to see it; the SDK doesn’t
mirror the local capture into remoteStream$.
To detect which participants are screen shares specifically, check
the participant’s metadata or type. In the kitchen-sink demo, the
share state is read from self.screenShareStatus directly:
Audio with the share
getDisplayMedia() can capture system / tab audio on some platforms
(Chromium-based browsers on macOS / Windows). The SDK forwards
whatever the browser provides — there’s no separate “share audio”
toggle. If the user’s browser doesn’t support display audio, only
the video is shared.
In practice:
- Chrome / Edge on macOS or Windows: works for tab audio, may work for system audio depending on OS version.
- Firefox: video only.
- Safari: video only (and screen sharing requires explicit user-initiated permission per session).
Browser quirks
- User gesture required.
startScreenShare()must originate from a click or keyboard event — calling it on a timer or after an async chain that didn’t start from a gesture will fail. - iOS Safari. Tab screen sharing isn’t supported. iOS has its own system-wide screen-broadcast flow which is outside the browser’s reach.
- Multiple displays. The picker lists each display separately; selecting “Entire screen” on a multi-monitor setup shares only the picked display.
Stopping from outside the page
The user can stop sharing from the browser’s “Stop sharing” toolbar.
When they do, the underlying track ends and the SDK transitions
screenShareStatus$ to 'idle' automatically — no action required
on your side. Subscribe to screenShareStatus$ and your UI will
update without polling.
Reference
SelfParticipant.startScreenShare()— open picker, add the share trackSelfParticipant.stopScreenShare()— remove the share trackSelfParticipant.screenShareStatus$/screenShareStatus— reactive status ('idle' | 'starting' | 'started' | 'stopping')SelfCapabilities.screenshare$— capability gate