DEV Community

Cover image for Record and share the screen from the browser — getDisplayMedia, and why Electron does the picker differently
Oleksandr Trukhnii
Oleksandr Trukhnii

Posted on

Record and share the screen from the browser — getDisplayMedia, and why Electron does the picker differently

A standalone companion to my FieldKit PWA series. In part 3 I covered capturing the camera and mic with getUserMedia; this is its sibling — capturing the screen with getDisplayMedia. It didn't belong in a field-notes app, so here it gets its own post — with a working recorder you can drop into any page, and a look at how the same job works in Electron.

The camera's sibling API

If you've used getUserMedia to grab a webcam stream, you already know 90% of screen capture. navigator.mediaDevices.getDisplayMedia() returns a MediaStream exactly like getUserMedia does — it just captures a screen, window, or browser tab instead of a camera. Feed that stream into MediaRecorder (same as part 3) and you have a screen recorder in a few dozen lines. No extension, no native host, no plugin.

Here's the whole capture call:

const stream = await navigator.mediaDevices.getDisplayMedia({
  video: { frameRate: 30 },
  audio: true, // system/tab audio — support varies (more on this below)
});
video.srcObject = stream; // live preview
Enter fullscreen mode Exit fullscreen mode

That single call triggers the browser's built-in picker: the user chooses what to share and clicks Share. You never see a list of windows — the browser owns that UI entirely. Hold that thought; it's the whole difference from Electron.

Recording the stream

MediaRecorder is identical to the audio recording from the media part — collect chunks, assemble a Blob on stop:

const recorder = new MediaRecorder(stream, { mimeType: pickMime() });
const chunks = [];
recorder.ondataavailable = (e) => e.data.size && chunks.push(e.data);
recorder.onstop = () => {
  const blob = new Blob(chunks, { type: recorder.mimeType });
  const url = URL.createObjectURL(blob); // play it back or offer a download
};
recorder.start();
Enter fullscreen mode Exit fullscreen mode

And don't hard-code the format — Chromium records WebM (VP9/VP8), Safari leans to MP4, so ask what's supported:

function pickMime() {
  const candidates = ["video/webm;codecs=vp9", "video/webm;codecs=vp8", "video/webm", "video/mp4"];
  return candidates.find((t) => MediaRecorder.isTypeSupported?.(t)) || "";
}
Enter fullscreen mode Exit fullscreen mode

The three details that separate a demo from a real feature

The happy path is short. These are the parts that trip people up in production.

1. React to the browser's own "Stop sharing" bar

While capturing, the browser shows its own floating "Stop sharing" control. When the user clicks that (not your Stop button), your MediaRecorder keeps running against a dead track unless you listen for it. The video track fires ended:

const track = stream.getVideoTracks()[0];
track.addEventListener("ended", () => stopRecording()); // user hit the browser's Stop
Enter fullscreen mode Exit fullscreen mode

Miss this and you get a broken recording and a confused user. It's the single most common screen-capture bug.

2. Always release the tracks

Just like the camera in part 3, a screen capture stays active — with the "sharing" indicator up — until you stop every track:

stream.getTracks().forEach((t) => t.stop());
Enter fullscreen mode Exit fullscreen mode

3. Know what the user actually picked

The user might share a whole monitor, one window, or a single tab — and that changes resolution, frame rate, and whether you got audio. getSettings() tells you:

const s = stream.getVideoTracks()[0].getSettings();
// s.displaySurface -> "monitor" | "window" | "browser"
// s.width, s.height, s.frameRate
Enter fullscreen mode Exit fullscreen mode

displaySurface is genuinely useful — a tab share behaves very differently from a full-monitor share, and you often want to adapt (or warn) based on it.

About audio — set expectations

audio: true requests audio, but this is the flakiest part of screen capture. Tab audio is well supported in Chromium; full-system audio is patchy and OS-dependent; on macOS especially, capturing system audio is limited. Request it, but check stream.getAudioTracks().length and don't promise users audio you can't reliably deliver.

A complete, working recorder

Putting it together — capture, live preview, getSettings() readout, ended handling, playback, and a download link — is a single self-contained HTML file.

Web screen recorder

Check live working demo or grab the full demo here (drop it on any HTTPS page or localhost and it just works). The core is exactly the pieces above; the file just adds UI and error handling.

Honest support picture

  • getDisplayMedia: supported in Chromium, Firefox, and Safari on desktop, secure-context only, and always user-gesture triggered.
  • iOS/iPadOS: not supported. Screen recording on iPhone/iPad is a system feature, not a web API — this is a desktop-first capability, full stop. (Another reason it didn't belong in FieldKit, which is built to shine on a phone in the field.)
  • Audio capture: tab audio > system audio; macOS system audio is especially limited. Feature-detect, don't assume.
  • Output format differs by browser — detect the mime type rather than hard-coding it.

Check caniuse: getDisplayMedia before relying on specifics.

How this compares to Electron — and where you get your own picker

Here's the payoff, and it's the reason I wanted to write this as a bridge rather than a plain tutorial.

On the web, you cannot build your own source picker. getDisplayMedia() shows the browser's picker — you can't style it, pre-select a window, or replace it. That's a deliberate security boundary: the browser guarantees the user consciously chose what to share.

Electron lets you own that experience. Because Electron has a privileged main process, its desktopCapturer API can enumerate every screen and window yourself — with thumbnails and titles — and render a fully custom picker dialog in your own UI. You then feed the chosen source ID back into getUserMedia with Electron's chromeMediaSource constraints. It's the same underlying capture, but you control the selection UX end to end.

I wrote up exactly how to build that custom Electron picker a while back, and it still holds up: How to build an Electron desktopCapturer screen picker dialog. If you've read this far, that's the natural next step for the desktop side.

The trade-off in one line: the web gives you screen recording with zero setup but a picker you don't control; Electron gives you a picker you fully control at the cost of shipping a desktop app. Same capture engine underneath — different amount of the experience in your hands.

Try it

Open the demo or grab the source code and open it over localhost or any HTTPS page, click Start, pick a window or tab, and record. Then read the Electron picker article if you want the desktop version where the picker is yours.

Top comments (0)