0Support

HTML <usermedia> playground

Explore <usermedia> as a trusted, browser-managed control for requesting camera and microphone access. Configure setConstraints(), connect the MediaStream to a preview, and compare the native element with progressive enhancement based on getUserMedia().

Ask AI

HTML <usermedia> playground

Response generated with openai/gpt-5.4-nano. AI can make mistakes. Always review the result.
Checking support…

Camera and microphone

The preview will appear here after you grant camera access.

Activate the control to request camera and microphone access.

HTML
<section class="usermedia-demo" aria-labelledby="usermedia-title">
  <h2 id="usermedia-title">Camera and microphone</h2>

  <div class="stream-preview">
    <video
      id="media-preview"
      autoplay
      playsinline
      muted
      hidden
      aria-label="Camera preview"
    ></video>
    <p data-media-placeholder>The preview will appear here after you grant camera access.</p>
  </div>

  <div class="media-actions">
    <usermedia id="media-control" aria-describedby="media-status">
      <button type="button" id="fallback-media-control">
        Enable camera and microphone
      </button>
    </usermedia>
    <button type="button" id="stop-media" disabled>
      Stop capture
    </button>
  </div>

  <p id="media-status" role="status" aria-live="polite">
    Activate the control to request camera and microphone access.
  </p>
</section>
JS
const mediaControl = document.querySelector('#media-control');
const fallbackButton = document.querySelector('#fallback-media-control');
const videoPreview = document.querySelector('#media-preview');
const placeholder = document.querySelector('[data-media-placeholder]');
const stopButton = document.querySelector('#stop-media');
const status = document.querySelector('#media-status');

const constraints = {
  video: {
    width: 1280,
    height: 720,
    facingMode: "user",
    frameRate: 30,
  },
  audio: {
    echoCancellation: true,
    noiseSuppression: true,
    autoGainControl: true,
  },
};
let currentStream = null;

const showStatus = (message) => {
  status.textContent = message;
};

const attachStream = (stream) => {
  if (!stream) return;

  currentStream = stream;
  videoPreview.srcObject = stream;
  videoPreview.hidden = false;
  placeholder.hidden = true;
  stopButton.disabled = false;
  showStatus("Capture is active. Video is muted to prevent feedback.");
};

const stopStream = () => {
  currentStream?.getTracks().forEach((track) => track.stop());
  currentStream = null;
  videoPreview.srcObject = null;
  videoPreview.hidden = true;
  placeholder.hidden = false;
  stopButton.disabled = true;
  showStatus("Capture has stopped.");
};

if ('HTMLUserMediaElement' in window) {
  mediaControl.setConstraints(constraints);

  mediaControl.addEventListener('stream', () => {
    attachStream(mediaControl.stream);
  });

  mediaControl.addEventListener('error', () => {
    const errorName = mediaControl.error?.name ?? 'UnknownError';
    showStatus(`Capture could not start: ${errorName}.`);
  });

  mediaControl.addEventListener('cancel', () => {
    showStatus("The permission request was cancelled.");
  });
} else {
  fallbackButton.addEventListener('click', async () => {
    if (!navigator.mediaDevices?.getUserMedia) {
      showStatus("Media capture is unavailable in this context or browser.");
      return;
    }

    showStatus("Waiting for the browser…");

    try {
      attachStream(await navigator.mediaDevices.getUserMedia(constraints));
    } catch (error) {
      const errorName = error instanceof DOMException ? error.name : 'UnknownError';
      showStatus(`Capture could not start: ${errorName}.`);
    }
  });
}

stopButton.addEventListener('click', stopStream);