MediaStream API — the pragmatic standard

The MediaStream API (part of the WebRTC family) has been available in all mainstream browsers since 2015. It lets a web page — after explicit permission — access the webcam, smartphone camera, and microphone. By 2026 the stack has stabilized and is mainstream-ready for profile photo upload, QR scanning, live filters, AR effects, and document scanning.

The basic pattern

async function startCamera() {
  try {
    const stream = await navigator.mediaDevices.getUserMedia({
      video: {
        facingMode: "user",      // "environment" for the rear camera
        width: { ideal: 1920 },
        height: { ideal: 1080 }
      },
      audio: false
    });
    const video = document.querySelector("video");
    video.srcObject = stream;
    await video.play();
  } catch (err) {
    console.error("Camera access denied or unavailable:", err);
  }
}

Permissions and UX

getUserMedia() triggers a browser permission dialog. Key UX practices:

  • Never ask on page load. Only after an explicit user action (a button click) — otherwise users deny out of hand.
  • Explain why beforehand. A short note like "We use the camera for your profile photo" ahead of the browser prompt measurably increases the approval rate.
  • Offer a fallback. If permission is denied, provide a classic file upload as plan B.
  • Permission is remembered. Chrome stores the answer per site — on the second visit no dialog is shown.
300 × 250 — Rectangle
Cookie-Banner ausstehend

Creating a snapshot in a canvas

Turning a running video stream into a static snapshot:

function takeSnapshot(video) {
  const canvas = document.createElement("canvas");
  canvas.width = video.videoWidth;
  canvas.height = video.videoHeight;
  const ctx = canvas.getContext("2d");
  ctx.drawImage(video, 0, 0);
  return canvas.toBlob(blob => {
    const url = URL.createObjectURL(blob);
    document.querySelector("img.preview").src = url;
  }, "image/jpeg", 0.9);
}

The blob can be uploaded directly as a profile photo, stored in IndexedDB, or processed further — for example with background removal.

Use case 1 — profile photo upload

The classic: the user takes a selfie for their profile. Best practice:

  1. Camera with facingMode: "user" for the selfie cam.
  2. Live preview with a soft crop box in the overlay (e.g. a 400×400 circle).
  3. Mirror the preview (CSS transform: scaleX(-1)) — users see themselves as in a mirror.
  4. On capture: save not mirrored.
  5. Strip EXIF on the server (see the EXIF post).

Use case 2 — QR code scanning

Browser QR scanning without a native app. Stack: getUserMedia() + canvas + the zxing-wasm library (a WebAssembly port of ZXing). Performance on mid-range smartphones: 5–10 FPS, enough for a stable scan.

Important: use the rear camera via facingMode: "environment". On smartphones autofocus is decisive — enable it via applyConstraints({ focusMode: "continuous" }).

300 × 250 — Rectangle
Cookie-Banner ausstehend

Use case 3 — document scanning

Digitizing a passport photo, a contract, a receipt. Pipeline:

  1. Camera stream in fullscreen.
  2. Edge detection via OpenCV.js or the newer shape-detector-api (Chrome).
  3. Auto-crop to the detected document outline.
  4. Perspective correction (trapezoid → rectangle).
  5. Black-and-white conversion with adaptive thresholding.
  6. Export as PNG or PDF.

In 2026, browser-only document scanners are competitive with native apps like Scanner Pro or Adobe Scan — with the privacy advantage that nothing gets uploaded.

Use case 4 — AR preview

Furniture shoppers want to see how the chair looks in their own living room. Browser AR is production-ready in 2026 via:

  • WebXR Device API: native AR sessions in Chrome on Android ARCore devices and Safari on iPhones (since visionOS 1.0).
  • MediaStream + Three.js/AR.js: poor man's AR via marker tracking, works in any browser.
  • Model-viewer web componentfrom Google: embeds a 3D model with a "View in AR" button that launches iOS Quick Look or Android Scene Viewer.

Use case 5 — live background effects

Processing the video stream live: background blur (like Zoom), green-screen replacement, beauty filters. Stack:

  1. getUserMedia() stream.
  2. Frame by frame via requestVideoFrameCallback.
  3. Segmentation with MediaPipe or BiRefNet (see the background removal post).
  4. Composite with the replacement background on a second canvas.
  5. Output stream via captureStream() into a new video element.

Performance: on an M2 CPU with WebGPU, 30+ FPS at 720p. On mid-range Android with MediaPipe, ~15–20 FPS — acceptable.

300 × 250 — Rectangle
Cookie-Banner ausstehend

Browser compatibility in 2026

  • Chrome 110+: the full MediaStream API plus WebGPU-accelerated processing.
  • Safari 17+: stable on macOS and iOS. iOS 17 finally brought PWA camera access without a Safari detour.
  • Firefox 130+: full MediaStream, WebGPU stable since 2025.
  • Edge: Chromium-based, identical to Chrome.
  • Mobile in-app browsers (TikTok, Instagram): often restricted. If a user opens your link from inside an app, camera access may be missing.

Privacy in practice

  • Always stop the stream when the feature is done: stream.getTracks().forEach(t => t.stop()). Otherwise the camera keeps running and the LED stays on.
  • Never record unexpectedly. The MediaRecorder API can record audio/video. If you use it, get explicit consent.
  • HTTPS is mandatory. getUserMedia() only works over HTTPS or on localhost. Safe against MITM eavesdroppers.

HTML fallback without MediaStream

On mobile there is a nice classic route: <input type="file" accept="image/*" capture="user">opens the camera app directly on iOS and Android. No permission dialog, no stream management. Not as flexible as MediaStream, but for "take a photo and upload it" use cases it's often the simpler choice.

300 × 250 — Rectangle
Cookie-Banner ausstehend

Tool recommendations

  • The web API directly: for simple photo capture and profile photo upload.
  • html5-qrcode or zxing-wasm: for QR scanning.
  • MediaPipe: for face detection, segmentation, pose estimation.
  • OpenCV.js: for classic image-processing algorithms.
  • Google's model-viewer: for AR previews without your own 3D stack.

Sources

MDN — getUserMedia · W3C — Media Capture and Streams · Google MediaPipe · zxing-js library · OpenCV.js · W3C — WebXR Device API · model-viewer (Google) · web.dev — Capturing Images.