Skip to content

Quick start

import { useEffect, useRef } from 'react';
import useMediaStream from 'use-media-stream';
function Camera() {
const { stream, isStreaming, error, start, stop } = useMediaStream();
const videoRef = useRef<HTMLVideoElement>(null);
useEffect(() => {
if (videoRef.current) videoRef.current.srcObject = stream;
}, [stream]);
if (error) return <p>{error.message}</p>;
return (
<>
<video ref={videoRef} autoPlay playsInline muted />
<button onClick={isStreaming ? stop : start}>{isStreaming ? 'Stop' : 'Start'}</button>
</>
);
}

That’s the whole thing. Navigate away mid-stream and the camera turns off on its own — there is no cleanup for you to write.

start() asks the browser for a camera and microphone, which triggers the permission prompt the first time. It resolves to the MediaStream, or to null if something went wrong — it never throws. The reason lands in error.

stream is the MediaStream itself, or null. Assigning it to a <video> element’s srcObject is what puts the picture on screen; React has no prop for that, hence the ref and the effect.

stop() stops every track and clears the stream, which is what actually turns the camera light off.

error is an Error you can render directly. Permission denials arrive here as a DOMException with name: 'NotAllowedError'.

The <video> element is muted deliberately. Without it you get your own microphone played back through your speakers, which feeds back. It mutes playback only — the audio track in the stream is untouched, so recording or transmitting still captures sound.

autoPlay and playsInline matter too: without them the video stays a blank frame, and on iOS it tries to open fullscreen.

The default asks for both camera and microphone. If you only want video, say so — the permission prompt then only mentions the camera:

useMediaStream({ mediaDeviceConstraints: { audio: false } });

See Constraints for resolution, frame rate and facing mode.

Browsers require a user gesture before granting camera access, so start() belongs in an event handler. Calling it from an effect on mount will be rejected in most browsers, and it’s poor behaviour regardless — people don’t expect a page to switch their camera on by itself.