← Capability Elements: <usermedia> MVP

v151 · html · event reference

<usermedia> events: stream / cancel / error

Every acquisition attempt ends with exactly one terminal event: stream when capture starts, cancel when the user backs out of the permission flow, or error for every other failure. All three are plain Event objects fired at the element.

Syntax

// Event handler IDL attributes (HTMLMediaCaptureElementBase mixin):
attribute EventHandler onstream;
attribute EventHandler oncancel;
attribute EventHandler onerror;

// Both registration styles work:
const el = document.querySelector("usermedia");
el.addEventListener("stream", (event) => { /* ... */ });
el.onstream = (event) => { /* ... */ };

The events are named stream, cancel, and error, and the event objects are plain Event instances — no custom interface, no payload properties. Everything you need is on the element itself: read el.stream after stream, and el.error after cancel/error. Chromium defines the handler attributes in html_media_capture_element_base.idl.

Source: Media Capture and Streams Extensions — event handlers.

Inputs

Events carry no inputs. The listener receives one argument — the plain Event — whose only useful members are the generic ones (type, target, isTrusted). There is no init dictionary and no way to request additional detail: results and diagnostics live on the element's properties (stream, error), not on the event. Consequently there is also nothing to validate or default on the receiving side.

Source: Specification — event queueing (fires a plain Event); DOM Standard — Event interface.

stream event — outputs

Fired when activation succeeded and a media stream is available. Ordering guarantee: the browser assigns el.stream before queueing the event, so inside the listener el.stream is always a live MediaStream — attach it (video.srcObject = el.stream), clone it, or read its tracks immediately. This is the success path of exactly one acquisition attempt; a later re-acquisition (after the previous stream ended) fires a fresh stream event with a new MediaStream.

Source: Specification — activation start stream steps; Chromium user_media_request_provider_impl.cc — OnSuccess.

cancel event — outputs

Fired when activation failed because of the user's choice: the user dismissed or denied the permission prompt (Chromium also routes getUserMedia()'s not-allowed-by-user result here). el.error holds a NotAllowedError DOMException (“Permission dismissed” or “Permission denied”). Treat it as a soft outcome, not a bug: the right response is a quiet inline explanation that the user can click again — which is also the element's built-in recovery story for a previously-denied permission.

Source: Specification — oncancel handler; Chromium html_media_capture_element_base.cc — OnEmbeddedPermissionsDecided.

error event — outputs

Fired when activation failed for reasons other than user choice, with no stream available. el.error holds the cause: InvalidStateError for a programmatic/gesture-less activation, NotSupportedError for a constraints mismatch, OperationError for stream-creation failure, or the underlying getUserMedia() rejection (NotFoundError, NotReadableError, OverconstrainedError, …). Note for handler authors: this is an error event on the element, unrelated to window-level error handling — it does not bubble into window.onerror.

Source: Specification — onerror handler; Chromium user_media_request_provider_impl.cc — OnError.

Errors

Event dispatch itself has no failure mode visible to the page: the events are queued as element tasks and always delivered. An exception thrown inside your listener follows the usual event-handler rules — it is reported as an uncaught error and does not stop the other listeners or affect the element's state machine. The only real trap is assuming an event means more than it does: stream proves the stream started, not that it is still live seconds later — monitor MediaStreamTrack state (ended, mute/unmute) for that.

Source: Specification — queue an event steps; DOM Standard — event dispatch.

Context

Target and receiver: the events fire at the <usermedia> element itself; listeners attach like any element listener. Trust: because acquisition only starts from a trusted gesture, these events always originate from real user interaction — they are dispatched by the browser (isTrusted === true) and cannot be forced by synthetic clicks, which are rejected with an InvalidStateError on the error path. Availability: Chrome 151, enabled by default; window exposure only.

Source: Specification — activation behavior and trusted events; Chromium runtime_enabled_features.json5; chromestatus.com/feature/4926233538330624.

Lifecycle

The events are the state machine's edges. Per attempt the ordering is: trusted click → (permission flow if needed) → getUserMedia() → exactly one terminal event, queued as a task on the element's media-capture task source (so it is asynchronous even when it could resolve synchronously — always register listeners before the user can click, ideally right after parsing the element). While a request is in flight, further activations are coalesced; while a stream is active, further clicks start nothing. After any terminal event the element is re-armed: the next trusted click begins a new attempt and will produce its own single terminal event.

Source: Specification — shared algorithms (task source, queue an event); Explainer — user journey and interaction model.

stream event — examples

const el = document.querySelector("usermedia");
const video = document.querySelector("video");

el.addEventListener("stream", () => {
  // el.stream is already populated — safe to use immediately.
  video.srcObject = el.stream;
  for (const track of el.stream.getTracks()) {
    track.addEventListener("ended", () => {
      // The element resets to its request state on its own;
      // update your UI to match.
      video.srcObject = null;
    });
  }
});
Source: Explainer — acquisition success example.

cancel event — examples

el.addEventListener("cancel", () => {
  // Soft outcome: the user dismissed or denied the prompt.
  console.info(`capture not granted: ${el.error?.message}`);
  hint.textContent = "Camera and microphone stay off. Click the button whenever you're ready.";
});
Source: Explainer — user cancellation example.

error event — examples

el.addEventListener("error", () => {
  switch (el.error?.name) {
    case "NotFoundError":
      show("No camera or microphone found on this device.");
      break;
    case "NotReadableError":
      show("Your device is busy — close the other app using it, then retry.");
      break;
    default:
      show(`Capture failed (${el.error?.name ?? "unknown"}). Please try again.`);
  }
});
Source: Explainer — error handling example.

Compatibility

stream / cancel / error events — checked 2026-07-26
Engine / runtimeSupportNotes
Chrome / Edge (desktop)151Enabled by default (milestone listing; UserMediaElement status stable)
Chrome (Android)151Per the ChromeStatus feature detail (desktop + Android 151); not supported on Android WebView
FirefoxNot supported“Under consideration” (mozilla/standards-positions #1392)
SafariNot supportedNo signal (WebKit/standards-positions #651; WICG/PEPC #62)
BCDNo entryNo api/HTMLUserMediaElement.json in browser-compat-data as of 2026-07-26
WPTUpstream WPT coverage existshtml/semantics/permission-element/usermedia; also unit-tested in Chromium (core, modules)

Naming caution for cross-browser futures: the superseded WICG/PEPC draft used streamready and userchangedenabled — those events do not exist in the shipping MVP; code written against the draft must be updated.

Source: ChromeStatus API feature record; WICG/PEPC usermedia_element.md (superseded draft).

Security and privacy

The events are outcome signals for a consent flow, and they necessarily reveal its result — a cancel tells the page the user declined, which the imperative getUserMedia() rejection already revealed, so no new disclosure class. They fire only at the element that mediated the request and carry no payload, so there is no cross-origin or cross-element channel. Because dispatch requires a trusted gesture upstream, scripts cannot synthesize fake stream events that would be confused with real ones — a listener can trust that a received event came from the browser.

Source: Explainer — privacy and security considerations; Specification — trusted-event filtering.