Feature spec
AdvancedCategory
communication-social
Build with AI
1-3 days with Lovable or V0 + manual WebRTC wiring
Custom build
2-4 weeks custom dev
Running cost
$0-10/mo up to 100 users · $200-600/mo at 10K users
Works on
Everything it takes to ship Screen Sharing — parts, prompts, and real costs.
Screen sharing requires three pieces: the browser Screen Capture API (getDisplayMedia) to capture the display, WebRTC (RTCPeerConnection) to stream it peer-to-peer, and a signaling channel (Supabase Realtime) to coordinate the handshake. With Lovable or V0 you can get a working prototype in 1-3 days, but the WebRTC peer logic always needs manual review. Running costs range from $0 at small scale to $200-600/mo at 10,000 users when a managed WebRTC service becomes mandatory.
What Screen Sharing Actually Requires
Screen sharing in a web app is not a single API — it is a chain of four cooperating systems. The browser's Screen Capture API (getDisplayMedia) captures a MediaStream of the user's display. WebRTC's RTCPeerConnection negotiates and streams that video to one or more viewers via STUN/TURN-mediated peer connections. A signaling channel (typically Supabase Realtime Broadcast) shuttles the SDP offer/answer and ICE candidates between peers during the handshake. And a session management layer tracks who is sharing, who is watching, and when the session ends. AI tools generate the UI shell and Supabase signaling reliably; the RTCPeerConnection wiring is where every first build breaks.
What users consider table stakes in 2026
- One-click share button that triggers the browser's native tab/window/full-screen selector dialog — no plugin or extension required
- Live thumbnail preview inside the selector dialog before sharing begins so users can confirm what they are sharing
- Persistent, clearly visible 'Stop sharing' button that remains on screen even when the user switches tabs
- Visual indicator (banner or badge) that sharing is active, visible to both the sharer and all viewers
- Viewer count or live attendee list showing who is watching in real time
- Graceful degradation: if getDisplayMedia is not supported (Firefox mobile, older Safari), show an unsupported message rather than a blank error
Anatomy of the Feature
Six components in total. The signaling channel and session UI generate reliably with AI tools. The WebRTC peer connection and screen capture layer are where builds consistently break on first attempt.
Screen capture layer
UICalls the browser-native MediaDevices.getDisplayMedia() API with video constraints ({ video: { cursor: 'always' }, audio: false }) to produce a MediaStream of the selected display surface. Must be triggered inside a direct user gesture (onClick), not in a useEffect or timer.
Note: getDisplayMedia requires HTTPS and a trusted browser context. Sandboxed iframes (Lovable preview, V0 sandbox) block it entirely — testing is only possible on the published URL.
WebRTC peer connection
BackendRTCPeerConnection manages the peer-to-peer video channel. The sharer creates an RTCPeerConnection, adds the MediaStream tracks, and creates an SDP offer. The viewer creates its own RTCPeerConnection, receives the offer, creates an answer, and both sides exchange ICE candidates. Uses public STUN servers (stun.l.google.com:19302) for NAT traversal and Twilio TURN for users behind corporate firewalls.
Note: P2P WebRTC works for 1-3 simultaneous viewers. Beyond that, each additional viewer consumes a full upstream stream from the sharer's device; switch to an SFU (Livekit or Daily.co) for 4+ viewers.
Signaling channel
BackendSupabase Realtime Broadcast channel (named by room_id) exchanges SDP offers/answers and ICE candidates between sharer and viewer during the WebRTC handshake. Broadcast is used (not INSERT) because signaling messages are ephemeral and do not need persistence.
Note: Realtime Broadcast has no message history. If a viewer joins late and misses ICE candidates, the connection fails. Implement a retry with renegotiation or store ICE candidates temporarily in a Supabase table as a fallback.
Session management UI
UIReact component built with shadcn/ui Dialog (for pre-share confirmation) and Badge (sharing status indicator). Manages the UI state machine: idle → requesting-permission → sharing → stopped. Shows active viewer count sourced from Supabase Realtime presence or the viewers table.
Note: The stop-sharing button must call stream.getTracks().forEach(t => t.stop()) to release the OS-level capture indicator — forgetting this leaves the camera/screen indicator running in the browser.
Recording layer (optional)
ServiceThe MediaRecorder API can capture the MediaStream locally to a Blob, then upload to Supabase Storage for cloud persistence. For managed cloud recording, Daily.co and Livekit Cloud both offer built-in record features at approximately $0.004/min recorded.
Note: Local MediaRecorder recording is free but lost if the user closes the tab. Cloud recording via Daily.co or Livekit requires upgrading beyond their free tiers for sessions over ~8 hours/month.
Permissions guard
UIChecks MediaDevices.getDisplayMedia support before showing the share button. On permission denial, catches the NotAllowedError and surfaces a friendly explainer with a retry button rather than letting the error propagate to a blank state.
Note: Firefox on Android and all iOS browsers prior to iOS 17 do not support getDisplayMedia — show an 'unsupported browser' message for these platforms rather than a broken UI.
The data model
Screen sharing needs two tables: sessions tracks active and past sharing sessions, and viewers records who joined each session. Run this SQL in the Supabase SQL editor (Dashboard → SQL Editor):
1create table public.screen_sessions (2 id uuid primary key default gen_random_uuid(),3 host_user_id uuid references auth.users(id) on delete cascade not null,4 room_id text not null unique default gen_random_uuid()::text,5 started_at timestamptz not null default now(),6 ended_at timestamptz,7 viewer_count integer not null default 0,8 status text not null default 'active' check (status in ('active', 'ended'))9);1011create table public.screen_session_viewers (12 id uuid primary key default gen_random_uuid(),13 session_id uuid references public.screen_sessions(id) on delete cascade not null,14 user_id uuid references auth.users(id) on delete cascade not null,15 joined_at timestamptz not null default now(),16 left_at timestamptz,17 unique (session_id, user_id)18);1920alter table public.screen_sessions enable row level security;21alter table public.screen_session_viewers enable row level security;2223create policy "Host can manage own sessions"24 on public.screen_sessions for all25 using (auth.uid() = host_user_id)26 with check (auth.uid() = host_user_id);2728create policy "Any authenticated user can view active sessions"29 on public.screen_sessions for select30 using (auth.role() = 'authenticated');3132create policy "Viewers can insert own join record"33 on public.screen_session_viewers for insert34 with check (auth.uid() = user_id);3536create policy "Viewers can see participants in a session"37 on public.screen_session_viewers for select38 using (auth.role() = 'authenticated');3940create policy "Viewers can update own leave record"41 on public.screen_session_viewers for update42 using (auth.uid() = user_id);4344create index screen_sessions_room_id_idx45 on public.screen_sessions (room_id);4647create index screen_session_viewers_session_idx48 on public.screen_session_viewers (session_id, joined_at desc);Heads up: The room_id column is what both peers subscribe to on the Supabase Realtime Broadcast channel. Generate it server-side when the host starts a session and share it with viewers via an invite link or in-app notification.
Build it — pick your path
Each tool fits this feature differently. Switch tabs to compare fit, time, the exact steps, and a copy-paste prompt.
The right path when screen sharing is a core product feature rather than a supplement. Full control over STUN/TURN configuration, codec selection (VP8 vs VP9 vs H.264), multi-viewer SFU architecture, cloud recording, and bandwidth optimization.
Step by step
- 1Integrate the Daily.co SDK or Livekit JS SDK — both abstract away RTCPeerConnection and SFU routing, giving production-grade reliability on day one
- 2Configure a Livekit self-hosted deployment or Daily.co app with custom TURN server credentials for enterprise NAT traversal
- 3Implement multi-viewer fan-out via SFU: sharer sends one upstream; the SFU distributes to N viewers without sharer CPU/bandwidth scaling linearly
- 4Add cloud recording via the platform's built-in record API; store recordings in Supabase Storage or S3 with signed URL access
- 5Instrument bandwidth and connection quality metrics to detect degraded sessions and prompt users to lower resolution
Where this path bites
- Custom WebRTC development has a steep learning curve; budget at least a week purely on ICE/NAT edge cases before writing app logic
Third-party services you'll need
Screen sharing core (capture + peer connection) is free using browser-native APIs and public STUN servers. Costs appear when users are behind corporate firewalls (TURN relay) or when you need more than 3 simultaneous viewers (managed SFU):
| Service | What it does | Free tier | Paid from |
|---|---|---|---|
| Supabase Realtime | Signaling channel for SDP offer/answer and ICE candidate exchange between peers | Free tier — 500 concurrent connections | Pro $25/mo — 10,000 concurrent connections |
| Twilio TURN (Network Traversal Service) | TURN relay server for users behind corporate firewalls and symmetric NAT where STUN alone fails | STUN only — free | TURN relay approx $0.004/GB relayed (approx) |
| Daily.co | Managed WebRTC with built-in screen share SDK, SFU for multi-viewer, and optional cloud recording | 2,000 participant-minutes/mo free | $0.00099/participant-minute beyond free tier (approx) |
| Livekit Cloud | Open-source WebRTC infrastructure with screen share, SFU, and egress recording | 50 GB egress/mo free | $0.50/GB egress beyond free tier (approx) |
Swipe the table sideways to see pricing.
What it costs to run
Drag through the tiers to see how your monthly bill scales with users — no surprises later.
Estimated monthly running cost
Supabase free tier covers signaling; public STUN servers are free; the only cost is TURN relaying for roughly 10% of users on strict corporate NAT, estimated at $5/mo for light usage.
Estimates use verified 2026 pricing and assume typical usage per user. Your real bill depends on activity, storage, and third-party plans.
What breaks when AI tools build this
The failures people actually hit on their first build — the symptom, why it happens, and the exact fix.
Screen is blank or black in preview — works nowhere except the published URL
Symptom: getDisplayMedia requires both a user gesture AND a secure (HTTPS) browsing context. Lovable's preview panel and V0's sandbox both run inside sandboxed iframes that block the Screen Capture API entirely — calling getDisplayMedia in these contexts returns a blank black stream or throws NotAllowedError with no visible error message.
Fix: Test screen sharing only on the published URL. In Lovable, click Publish first; in V0, deploy to Vercel. Never report a bug to the AI tool until you've verified on the live HTTPS URL.
ICE connection fails for some users — especially on corporate networks
Symptom: Public STUN servers help peers discover their external IP addresses, but symmetric NAT (common in enterprise VPNs and firewalls) blocks direct peer connections even with STUN. When STUN fails, the RTCPeerConnection silently stays in 'connecting' state and never streams video.
Fix: Add a TURN relay server to the iceServers config. Twilio's Network Traversal Service generates short-lived TURN credentials via a Supabase Edge Function that the client fetches before creating the peer connection. Without TURN, roughly 10-15% of users on strict networks will never connect.
Supabase Realtime drops signaling messages and negotiation fails silently
Symptom: Supabase Realtime Broadcast has no message persistence or delivery guarantee. If a peer's Broadcast subscription briefly disconnects (tab backgrounded, mobile browser, slow connection) and reconnects after an ICE candidate was sent, those candidates are lost and the WebRTC negotiation fails — often with no error visible to the user.
Fix: Implement offer/answer retry with exponential backoff: if the peer connection stays in 'connecting' for more than 8 seconds, trigger a full renegotiation. As a belt-and-suspenders backup, write ICE candidates to a temporary Supabase table with a 60-second TTL and have the joining peer read them on connect.
AI puts getDisplayMedia in a useEffect and it throws NotAllowedError
Symptom: The browser enforces that getDisplayMedia can only be called from a direct user-interaction handler (click, keypress). AI tools frequently scaffold it inside a useEffect, a setTimeout, or an async function called outside an event handler — all of which the browser treats as non-trusted-gesture contexts and blocks with NotAllowedError.
Fix: Ensure the getDisplayMedia call is inside the async onClick handler with no intermediary async steps between the click event and the API call. Move the call up the call stack if needed; the fewer async hops between user gesture and getDisplayMedia, the better.
CPU and bandwidth spike at 4+ simultaneous viewers
Symptom: P2P WebRTC creates a separate upstream peer connection per viewer. At 4 viewers the sharer's device sends 4 full video streams simultaneously. On a typical laptop with a 10 Mbps upload, a 720p screen share at 4 viewers saturates the connection entirely and frame rate drops to near zero.
Fix: Switch to an SFU (Selective Forwarding Unit) architecture for more than 3 simultaneous viewers. Daily.co and Livekit Cloud handle SFU routing transparently — the sharer sends one stream to the SFU which distributes to all viewers. P2P is fine for 1-on-1 and small group sessions.
Best practices
Always wrap getDisplayMedia in a try/catch and handle NotAllowedError, NotFoundError, and AbortError separately with user-friendly messages for each
Call stream.getTracks().forEach(t => t.stop()) on every exit path — tab close, route change, and the stop button — or the OS screen-capture indicator remains active after the session ends
Show the viewer count in real time using Supabase Realtime Presence rather than polling the DB to keep the UI responsive without generating unnecessary database load
Set a session timeout: automatically end sharing after 4 hours and notify the host, preventing orphaned sessions that accumulate in the database
Include the audio capture toggle as an explicit off-by-default option — many users are surprised that getDisplayMedia can capture system audio and tab audio on Chrome
Store session metadata (started_at, ended_at, peak_viewer_count) even for short sessions; this data is essential for support triage when users report connectivity issues
Document the browser support matrix clearly in your app's UI: screen sharing works in Chrome, Edge, and Firefox on desktop; Safari 13+; it does not work on iOS browsers or Firefox for Android
When You Need Custom Development
AI-assisted builds handle basic 1-on-1 and small-group screen sharing well. Certain requirements put you firmly in custom territory:
- More than 3 simultaneous viewers per session — P2P mesh architecture collapses and SFU integration (Daily.co SDK or Livekit) requires custom wiring beyond what AI tools reliably produce
- Mandatory TURN relay coverage for enterprise customers on strict corporate firewalls — TURN credential rotation and failover require backend infrastructure AI tools don't scaffold
- Cloud recording or searchable playback of shared sessions — integrating MediaRecorder with cloud storage, transcoding, and access control is a multi-week project
- Cross-device screen sharing (desktop-to-mobile, multi-tab orchestration, or virtual classroom layouts with simultaneous presenters) where session management complexity far exceeds AI tool output
RapidDev builds these features for production
Real auth, database, and payments — fixed price, delivered in weeks. You own 100% of the code.
Frequently asked questions
Can I build screen sharing without WebRTC?
There are workarounds, but none are practical for production. You could capture screenshots with getDisplayMedia and send them as images over WebSockets — but this gives 1-2 FPS at best and burns Supabase bandwidth. For real-time video, WebRTC is the only browser-native solution. Third-party SDKs like Daily.co and Livekit abstract it, but they use WebRTC underneath.
Is screen sharing possible in a mobile browser?
On Android Chrome, getDisplayMedia is supported from Chrome 94+. On iOS, screen sharing from a browser is not supported — Apple restricts getDisplayMedia on iOS Safari and all iOS browsers. For iOS screen sharing you need a native app using ReplayKit.
How do I stop AI from generating broken WebRTC code?
Be extremely specific in your prompt: name the exact callback where getDisplayMedia should be called (onClick handler), name the iceServers config, specify that ICE candidates are sent via Broadcast not INSERT, and list the exact error types to catch. Vague prompts produce working-looking code that fails at the ICE negotiation step. After generation, always verify the getDisplayMedia call is inside a trusted user-gesture handler.
What is the cheapest way to add screen sharing to a Lovable app?
For up to ~50 simultaneous sessions with small viewer counts (1-3 per session), the cheapest path is Supabase free tier for signaling plus public Google STUN servers (stun.l.google.com:19302) — total cost is $0/mo. Add Twilio TURN only if you have enterprise customers on strict firewalls. Avoid managed services like Daily.co unless you need SFU multi-viewer or cloud recording.
Does screen sharing work on Safari?
Yes, getDisplayMedia is supported on Safari 13+ on macOS. However, Safari's implementation has limitations: it does not support sharing individual application windows (only the entire screen or a tab), and audio capture via getDisplayMedia is not available in Safari. Test specifically in Safari before telling users it works.
Can viewers interact with the shared screen (remote control)?
No — browsers do not expose a remote-control API. WebRTC streams display pixels, not input events. True remote control (like TeamViewer) requires a native OS-level agent running outside the browser. For browser-only collaboration, the closest alternative is a shared whiteboard or a real-time cursor position overlay using Supabase Realtime Presence.
How do I record a screen share session?
Two options: local recording with the MediaRecorder API (free, records on the sharer's device, upload to Supabase Storage after) or cloud recording via Daily.co or Livekit Cloud (approximately $0.004/min, stores directly in their cloud). Local recording is lost if the tab crashes; cloud recording is reliable but adds cost. For most apps, local MediaRecorder + upload covers the use case at zero additional service cost.
What is the difference between getDisplayMedia and getUserMedia for screen sharing?
getUserMedia captures the user's camera and microphone. getDisplayMedia captures a selected display surface: a browser tab, an application window, or the entire screen. They produce the same type (MediaStream) so you can mix tracks from both — for example, combining a webcam feed with a screen share — by adding tracks from both calls to the same RTCPeerConnection.
Need this feature production-ready?
RapidDev builds screen sharing into real apps — auth, database, payments — at $13K–$25K.
Book a free consultation30-min call. No commitment.