use-media-stream
A React hook for getUserMedia that cleans up after itself.
Cameras, microphones, device switching and track muting — without the lifecycle bugs.
Documentation · Live demo · Changelog
Getting a camera stream is one line. Getting it to stop is where the bugs are — a component unmounts and the recording light stays on, a device list leaves an orphaned stream open, a track goes silent and your UI never notices.
This hook owns that lifecycle so you don't have to.
- Start, stop and swap devices with a small, predictable API
- Releases tracks on unmount — no lingering camera light
- Mute without dropping the device, so unmuting is instant
- Device enumeration split by kind, with live track settings
- Server-safe — renders in Next.js and Remix without a
typeof windowdance - Zero runtime dependencies, ESM + CJS, typed, published with provenance
Install
npm install use-media-stream
Requires react >= 16 as a peer dependency. Nothing else.
Upgrading from v1?
stop()and unmounting now release the stream in cases where they previously did not. See Migrating from v1.
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.
Documentation
Full guides and API reference at kothariji.github.io/use-media-stream:
| Quick start | A working camera in about fifteen lines |
| Streams and lifecycle | When the stream is released, and by what |
| Devices | Listing and switching cameras and microphones |
| Muting and track events | Why muting isn't stopping |
| Constraints | Resolution, frame rate, and how merging works |
| Server rendering | Next.js, Remix, static export |
| API reference | Everything the hook returns |
| Live demo | Every function, wired to your camera |
Development
npm install # one install at the root; docs/ is an npm workspace
npm run typecheck
npm test
npm run test:coverage
npm run build
The docs site doubles as the development playground — it imports the hook from src/, so edits
hot-reload against a real camera:
npm run docs # dev server, http://localhost:4321/use-media-stream/
npm run docs:stop # it runs detached, so it needs stopping explicitly
npm run docs:build
Install from the root, not from inside
docs/. The demo imports the hook fromsrc/, which resolves its dependencies from the root — a separate install indocs/gives you two copies of React in one page.
Verifying the published output
npm run verify:package
Packs the library and installs it into a scratch project the way a consumer would, then checks
require() resolves the CJS build, import resolves the ESM one, types resolve in every module
mode (attw), and package.json is sane (publint).
Worth running before any release. npm link and file: installs are not equivalent — they
skip packing and the files field, and both of the packaging bugs this library once shipped
survive them. CI runs this same script.
Releasing
npm version <patch|minor|major>
git push --follow-tags
A v* tag runs the full suite and publishes with provenance. Prereleases go to the next
dist-tag, so npm install use-media-stream is unaffected.
Contributing
Issues and pull requests welcome — open an issue to start.