← Capability Elements: <usermedia> MVP
v151 · html · property reference
HTMLUserMediaElement.stream / .error
The two read-only properties you use after an acquisition attempt: stream holds the captured MediaStream on success, and error holds the DOMException that explains a failure.
Syntax
// Specification IDL (Media Capture and Streams Extensions):
readonly attribute MediaStream? stream;
readonly attribute DOMException? error;
// Read them on the element:
const el = document.querySelector("usermedia");
const mediaStream = el.stream; // MediaStream or null
const failure = el.error; // DOMException or null
Chromium ships error on the HTMLMediaCaptureElementBase mixin (html_media_capture_element_base.idl) and stream as a partial mixin in modules/mediastream (html_user_media_element_media_stream.idl) — same observable surface as the specification.
Inputs
Both properties are read-only and take no inputs. There is no setter: assigning to el.stream or el.error is ignored in sloppy mode (and a TypeError in strict mode) — unlike the superseded WICG/PEPC draft, you cannot hand the element an externally-obtained MediaStream. The only way stream gets populated is a successful element-brokered acquisition; the only way error gets populated is a failed one.
stream — outputs
el.stream returns a MediaStream or null. It is null from construction until the first successful acquisition; on success the browser assigns the getUserMedia() result — one video track and/or one audio track per the effective constraints — before firing the stream event, so the property is always readable inside that event's listener. Use it exactly like any getUserMedia() result: video.srcObject = el.stream, enumerate tracks, apply constraints, stop tracks. Once the stream ends (all tracks ended, including via your own track.stop()), the element's UI returns to the request state; the property is not documented as being cleared, so gate usage on el.stream?.active rather than on the property being non-null.
error — outputs
el.error returns a DOMException or null. It is set on every failure path: NotAllowedError when the user dismisses or denies the prompt (fires cancel), InvalidStateError for activation without a genuine user gesture, NotSupportedError when constraints don't cover the requested capture kinds, OperationError if stream creation itself fails, and the raw getUserMedia() rejection (for example NotFoundError, NotReadableError, or an OverconstrainedError) for device-level failures (fires error). Read it inside the cancel/error event listeners — error.name and error.message are the diagnostic surface. One caveat: in the element's experimental legacy mode the getter hard-returns null.
Errors
The getters themselves never throw — they always return a value (null or the stored object). The error semantics live one level up: error is where the element reports failures, and the full cause-to-value mapping is in the overview's errors table. The one trap to avoid: don't branch on el.error === null as a success signal — check the terminal event first, because a previous attempt's DOMException can still be stored when the next attempt begins.
Context
Receiver: any HTMLUserMediaElement instance — the properties live on the prototype chain via the HTMLMediaCaptureElementBase mixin. Exposure: window only, secure context required for the element to function at all (in a non-secure context the element renders fallback content and acquisition never starts, so stream stays null). Availability: Chrome 151, enabled by default (UserMediaElement, stable); feature-detect with "HTMLUserMediaElement" in window before reading either property on an unknown-element fallback.
Lifecycle
Construction initializes both internal slots to null. Per acquisition attempt the transitions are: attempt starts → on success stream is assigned and the stream event queues; on failure error is assigned and cancel or error queues. The stream assignment outlives the event: the property keeps returning the same MediaStream while the element monitors it, and Chromium skips new requests while that stream is still active. When the tracks end, the element's control state resets and the next click starts a fresh acquisition (producing a new MediaStream, not reviving the old one). Neither property is reset by a subsequent attempt start — treat the terminal event, not property reads in isolation, as the synchronization point.
Examples
const el = document.querySelector("usermedia");
const video = document.querySelector("video");
el.addEventListener("stream", () => {
// el.stream is guaranteed populated here.
video.srcObject = el.stream;
console.log("tracks:", el.stream.getTracks().map(t => t.kind).join("+"));
});
el.addEventListener("cancel", () => {
// User bailed out of the prompt — not a bug, don't alarm anyone.
statusEl.textContent = `Access not granted (${el.error?.name}). Click to try again.`;
});
el.addEventListener("error", () => {
// Real failure: device busy, missing, or acquisition blew up.
statusEl.textContent = `Capture failed: ${el.error?.name} — ${el.error?.message}`;
});
Source: Explainer — error handling examples.
Compatibility
| Engine / runtime | Support | Notes |
|---|---|---|
| Chrome / Edge (desktop) | 151 | Enabled by default (milestone listing; UserMediaElement status stable) |
| Chrome (Android) | 151 | Per the ChromeStatus feature detail (desktop + Android 151); not supported on Android WebView |
| Firefox | Not supported | “Under consideration” (mozilla/standards-positions #1392) |
| Safari | Not supported | No signal (WebKit/standards-positions #651; WICG/PEPC #62) |
| BCD | No entry | No api/HTMLUserMediaElement.json in browser-compat-data as of 2026-07-26 |
| WPT | Upstream WPT coverage exists | html/semantics/permission-element/usermedia; also unit-tested in Chromium (core, modules) |
Security and privacy
stream hands the page real camera/microphone media — the same sensitivity class as a getUserMedia() result — but only after browser-mediated consent anchored to a trusted click; the element cannot be tricked into handing over a stream by synthetic events. error exposes the failure DOMException (names like NotAllowedError reveal the user's permission decision — again, no more than getUserMedia() rejections already reveal). Neither property touches storage, and there is no cross-origin channel: the MediaStream obeys the usual same-origin capture rules.