Skip to main content
Daily lets participants share their screen, an application window, or a browser tab with other call participants. startScreenShare() accepts one of two argument shapes — or no arguments at all:
  • Let Daily handle capture (DailyStartScreenShare) — Daily calls getDisplayMedia() for you, optionally using constraints and quality settings you provide. This is the recommended option for most use cases.
  • Bring your own stream (DailyStartScreenShareFromStream) — you call getDisplayMedia() yourself (or source a stream from Electron’s desktopCapturer, a canvas, etc.) and hand the resulting MediaStream to Daily.
To stop sharing: call.stopScreenShare(). startScreenShare() is non-blocking — the browser’s native picker opens asynchronously. Listen for the events below to know when sharing actually begins or is canceled.

Let Daily handle capture

Recommended Call startScreenShare() with no arguments to use Daily’s defaults, or pass a DailyStartScreenShare object to configure the capture:
displayMediaOptions
DailyDisplayMediaStreamOptions
Options forwarded directly to navigator.mediaDevices.getDisplayMedia(). Use these to request system audio, control surface switching, and more.
screenVideoSendSettings
DailyVideoSendSettings | DailyScreenVideoSendSettingsPreset
Controls the video encoding quality for the shared screen. Accepts either a preset string or a custom DailyVideoSendSettings object. See below for details.
// No arguments — Daily uses browser defaults
call.startScreenShare();

// With options
call.startScreenShare({
  displayMediaOptions: {
    audio: true,
    video: { frameRate: { ideal: 30, max: 60 } },
    selfBrowserSurface: 'exclude',
    systemAudio: 'include',
  },
  screenVideoSendSettings: 'detail-optimized',
});

DailyDisplayMediaStreamOptions

audio
boolean | MediaTrackConstraints
Request system audio in the screen share. Default is browser-dependent.
video
boolean | MediaTrackConstraints
Video constraints for the captured surface.
selfBrowserSurface
'include' | 'exclude'
Whether to include the current browser tab as a selectable source.
surfaceSwitching
'include' | 'exclude'
Whether to show an in-call surface-switching control (Chrome 107+).
systemAudio
'include' | 'exclude'
Whether to offer system audio capture in the picker (Windows/Chrome only).

Bring your own stream

If you already have a MediaStream — captured with your own getDisplayMedia() call, sourced from an Electron desktopCapturer, or otherwise constructed — pass it via DailyStartScreenShareFromStream. Daily will not call getDisplayMedia() in this case.
mediaStream
MediaStream
required
A MediaStream whose first video track (and optionally first audio track) will be used as the screen share source.
screenVideoSendSettings
DailyVideoSendSettings | DailyScreenVideoSendSettingsPreset
Optional quality preset or custom settings for the provided video track. See below.
const stream = await navigator.mediaDevices.getDisplayMedia({ video: true });
call.startScreenShare({ mediaStream: stream });

Screenshare streams vs. custom tracks

If you have a MediaStream you want to send, you have two options: pass it to startScreenShare({ mediaStream: stream }) or send it as a custom track via startCustomTrack(). These are not interchangeable — Daily treats screen share video tracks differently from other video tracks, and routing a stream through the wrong path will cause incorrect behavior. In Chromium-based browsers, a screen share video track becomes muted at the browser level whenever its content is not changing — for example, when a shared document is idle. This is normal browser behavior and does not mean the user stopped sharing or there are issues with the track. To avoid incorrectly firing track-stopped or transitioning to an interrupted state, Daily suppresses the muted signal on screen share video tracks. This suppression only applies to tracks sent via startScreenShare(). Use startScreenShare({ mediaStream: stream }) when your stream is screen content — a getDisplayMedia() capture, an Electron desktopCapturer stream, or any source that may go idle between updates. The muted-suppression handling will prevent spurious interruptions. Use startCustomTrack() when your stream is not screen content — a canvas animation, a virtual camera, or any source that should respond normally to the muted signal. Routing this through startScreenShare() would mask legitimate track state changes.

Send Settings

Both methods of screensharing allow you to define the quality of the sending stream by either providing a preset or passing a custom DailyVideoSendSettings object. DailyScreenVideoSendSettingsPreset provides four named presets optimized for different content types:
PresetBest for
'default-screen-video'General purpose (Daily’s default)
'detail-optimized'Text-heavy content — slides, IDEs, documents
'motion-optimized'Video playback, animations, fast UI
'motion-and-detail-balanced'Mixed content such as interactive demos
For full control, pass a DailyVideoSendSettings object:
call.startScreenShare({
  screenVideoSendSettings: {
    maxQuality: 'high',
    encodings: {
      low:  { maxBitrate: 500_000, maxFramerate: 5 },
      high: { maxBitrate: 4_000_000, maxFramerate: 30 },
    },
  },
});

Updating screen share state mid-share

Use updateScreenShare() to enable or disable the screen video or audio tracks after sharing has started — without stopping and restarting the entire screen share:
// Mute screen audio while keeping screen video active
call.updateScreenShare({
  screenVideo: { enabled: true },
  screenAudio: { enabled: false },
});

// Re-enable screen audio
call.updateScreenShare({
  screenVideo: { enabled: true },
  screenAudio: { enabled: true },
});

Reading local screen share state

const isVideoActive = call.localScreenVideo(); // true if screen video is active
const isAudioActive = call.localScreenAudio(); // true if screen audio is active

Events

local-screen-share-started

Fired when the local participant’s screen share begins. Useful for updating UI to show a “stop sharing” button.
call.on('local-screen-share-started', () => {
  stopSharingBtn.hidden = false;
  startSharingBtn.hidden = true;
});

local-screen-share-stopped

Fired when screen sharing ends — either because your code called stopScreenShare() or because the user clicked the browser’s built-in “Stop sharing” button.
call.on('local-screen-share-stopped', () => {
  stopSharingBtn.hidden = true;
  startSharingBtn.hidden = false;
});

local-screen-share-canceled

Fired when the user opens the browser picker but then dismisses it without selecting a source.
call.on('local-screen-share-canceled', () => {
  console.log('User dismissed the screen share picker.');
});

Detecting remote screen shares

Remote screen share tracks arrive via the standard track-started / track-stopped events with type === 'screenVideo' or type === 'screenAudio':
call.on('track-started', ({ participant, track, type }) => {
  if (type === 'screenVideo' && !participant.local) {
    const video = document.createElement('video');
    video.srcObject = new MediaStream([track]);
    video.autoplay = true;
    document.getElementById('screen-container').append(video);
  }
});

call.on('track-stopped', ({ participant, type }) => {
  if (type === 'screenVideo' && !participant?.local) {
    document.getElementById('screen-container').innerHTML = '';
  }
});

Complete example

const call = Daily.createCallObject();
await call.join({ url: 'https://your-domain.daily.co/room' });

const startBtn = document.getElementById('start-share');
const stopBtn  = document.getElementById('stop-share');

startBtn.onclick = () => {
  call.startScreenShare({
    displayMediaOptions: { audio: true, selfBrowserSurface: 'exclude' },
    screenVideoSendSettings: 'detail-optimized',
  });
};

stopBtn.onclick = () => call.stopScreenShare();

call.on('local-screen-share-started', () => {
  startBtn.disabled = true;
  stopBtn.disabled = false;
});

call.on('local-screen-share-stopped', () => {
  startBtn.disabled = false;
  stopBtn.disabled = true;
});

call.on('local-screen-share-canceled', () => {
  console.log('Picker was dismissed.');
});