Camera and microphone
The preview will appear here after you grant camera access.
Activate the control to request camera and microphone access.
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().
The preview will appear here after you grant camera access.
Activate the control to request camera and microphone access.
<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>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);