@formvu/react-media-kit
v0.3.45
Published
The starter is built on top of Vite 7.x and prepared for writing libraries in TypeScript. It generates a package with support for ESM modules and IIFE.
Downloads
542
Readme
@formvu/react-media-kit
A React library for WebRTC media capture — video recording (LiveKit, Red5 Pro, Cloudflare, local), photo capture, media stream management, and device performance monitoring.
Table of Contents
Installation
pnpm add @formvu/react-media-kitnpm install @formvu/react-media-kityarn add @formvu/react-media-kitRequirements
- Node.js >= 22
- React >= 19.0.0
- @tanstack/react-query 5.90.10
Quick Start
1. Set up the MediaStream provider
import { MediaStreamProvider } from "@formvu/react-media-kit";
function App() {
return (
<MediaStreamProvider
config={{ mediaType: "video", facingMode: "user" }}
callbacks={{
onStreamReady: stream => console.log("Stream ready"),
onError: error => console.error(error.message),
}}
renderLoading={() => <p>Loading camera...</p>}
renderError={(error, retry) => (
<div>
<p>{error.message}</p>
<button onClick={retry}>Retry</button>
</div>
)}
>
<Recorder />
</MediaStreamProvider>
);
}2. Record with LiveKit
import { useLiveKitRecorder, useMediaStream } from "@formvu/react-media-kit";
function Recorder() {
const { stream } = useMediaStream();
const { startRecording, stopRecording, status, error } = useLiveKitRecorder({
stream,
callbacks: {
onConnectionStateChange: state => console.log("Connection:", state),
onConnectionQualityChange: quality => console.log("Quality:", quality),
onError: error => console.error(error.type, error.message),
},
config: {
sessionId: "unique-session-id",
enabled: true,
apiUrl: "https://your-api.example.com",
serverUrl: "wss://your-livekit-server.example.com",
},
});
return (
<div>
<p>Status: {status}</p>
<button onClick={startRecording} disabled={status !== "connected"}>
Start
</button>
<button onClick={stopRecording} disabled={status !== "recording"}>
Stop
</button>
{error && <p>Error: {error.message}</p>}
</div>
);
}Features
Recorders
The library provides four recording backends. All share the same status lifecycle and error model.
Recorder Status Lifecycle
idle → connecting → connected → starting-recording → recording → stopping-recording → connectedCommon Error Types
| Type | Description |
| ------------------------ | ----------------------------------------- |
| connection-failed | Initial connection to server failed |
| connection-lost | Connection lost during active session |
| reconnection-failed | Failed to reconnect after connection loss |
| recording-start-failed | Failed to start recording |
| recording-stop-failed | Failed to stop recording |
| stream-unavailable | MediaStream is not available |
| tracks-unavailable | No tracks available for recording |
useLiveKitRecorder
Records via a LiveKit SFU server. Supports TURN configuration, connection quality monitoring, and automatic reconnection.
const recorder = useLiveKitRecorder({
stream,
callbacks: { onError, onConnectionStateChange, onConnectionQualityChange },
config: {
sessionId: "session-id",
enabled: true,
apiUrl: "https://api.example.com",
serverUrl: "wss://livekit.example.com",
turn: {
iceServers: {
urls: ["turn:turn.example.com:3478"],
username: "user",
credential: "pass",
},
},
},
});useRed5Recorder
Records via a Red5 Pro media server. Requires a video element in the DOM.
const recorder = useRed5Recorder({
stream,
callbacks: { onError, onConnectionQualityChange },
config: {
sessionId: "session-id",
enabled: true,
apiUrl: "https://api.example.com",
host: "red5.example.com",
app: "live",
mediaElementId: "red5-video",
streamMode: "append",
},
});useCloudflareRecorder
Records via Cloudflare Stream using WHIP.
const recorder = useCloudflareRecorder({
stream,
callbacks: { onError, onConnectionQualityChange },
config: {
sessionId: "session-id",
enabled: true,
apiUrl: "https://api.example.com",
apiToken: "cf-api-token",
accountId: "cf-account-id",
},
});useLocalRecorder
Records locally in the browser using the MediaRecorder API. No server required.
const { startRecording, stopRecording, isRecording, length, error } =
useLocalRecorder({
stream,
callbacks: {
onVideoReady: (url, file) => console.log("Video ready:", url),
onError: message => console.error(message),
onIntentionalStop: () => console.log("Stopped by user"),
},
config: { enabled: true },
});Photo Capturers
usePhotoCapturer
Capture a single photo from a MediaStream.
const { capturePhoto, clearPhoto, isCapturing, photoUrl } = usePhotoCapturer({
stream,
callbacks: {
onPhotoReady: (url, file) => console.log("Photo:", url),
onError: message => console.error(message),
},
config: { enabled: true, quality: 0.9 },
});useRecordingPhotoCapturer
Capture photos during an active recording session with optional auto-capture on an interval.
const { capturePhoto, photos, removePhoto, clearPhotos, isCapturing } =
useRecordingPhotoCapturer({
stream,
isRecording: true,
callbacks: {
onPhotoReady: photo => console.log("Captured at", photo.timestamp),
onError: message => console.error(message),
},
config: {
enabled: true,
quality: 0.9,
autoCapture: { enabled: true, intervalMs: 5000 },
},
});Media Stream
MediaStreamProvider
React context provider that manages camera/microphone access, zoom, torch, and camera switching.
<MediaStreamProvider
config={{
mediaType: "video", // 'video' | 'photo'
facingMode: "user", // 'user' | 'environment'
enableZoom: true,
enableTorch: true,
}}
callbacks={{
onStreamReady: (stream, settings) => {},
onError: error => {},
onStreamEnd: () => {},
onSettingsChange: settings => {},
onFacingModeChange: facingMode => {},
onPermissionsRequested: () => {},
}}
>
{children}
</MediaStreamProvider>useMediaStream
Access the stream context from any child component.
const {
stream, // MediaStream | null
settings, // { minZoomLevel, maxZoomLevel, activeZoomLevel, isTorchSupported, isTorchOn, facingMode }
isLoading,
error,
updateZoom, // (level: number) => void
updateTorch, // (on: boolean) => void
switchCamera, // () => void
initializeStream, // (facingMode?) => Promise<void>
stopStream, // () => void
} = useMediaStream();Device Monitor
useDeviceMonitor
Monitors device performance using the Compute Pressure API, frame drop detection, and long task tracking. Useful for adapting recording quality on lower-end devices.
const {
pressure, // 'nominal' | 'fair' | 'serious' | 'critical'
frameDropRate, // 0–1
isUnderPressure, // true when CPU is stressed or frames are dropping
deviceTier, // 'low' | 'mid' | 'high'
longTaskCount,
totalBlockedTime,
cpuCores,
deviceMemory,
reset,
} = useDeviceMonitor({
enabled: true,
sampleInterval: 2000,
frameDropThreshold: 0.15,
targetFps: 30,
onPressureChange: state => console.log("CPU pressure:", state),
onUnderPressureChange: isUnderPressure => {
if (isUnderPressure) downgradeQuality();
},
});API Reference
Connection Quality
All WebRTC recorders (LiveKit, Red5, Cloudflare) report connection quality:
| Value | Description |
| ----------- | ---------------------------- |
| excellent | Optimal connection |
| good | Acceptable quality |
| poor | Degraded, may affect quality |
| lost | Connection lost |
| unknown | Quality not yet determined |
Connection States
| State | Description |
| -------------- | -------------------------------- |
| connected | Successfully connected |
| disconnected | Connection lost |
| reconnecting | Attempting to reconnect |
| reconnected | Successfully reconnected |
| stopped | Connection intentionally stopped |
Shared Utilities
import {
sleep, // Promise-based delay
getRecorderError, // Helper to construct recorder errors
getRecorderStatus, // Helper to derive status
getVideo, // Get video element from stream
createMediaRecorder, // Create a MediaRecorder with best codec
getFileTypeExtension, // Get file extension for MIME type
supportsMediaRecorder, // Check browser MediaRecorder support
getMediaStream, // Standalone stream acquisition (no React)
stopMediaStream, // Stop all tracks on a stream
detectPlatform, // Detect iOS / Android / unknown
buildVideoConstraints, // Build MediaTrackConstraints for video
buildPhotoConstraints, // Build MediaTrackConstraints for photo
mapMediaError, // Map DOMException to typed MediaStreamError
applyZoomLevel, // Apply zoom to a video track
applyTorch, // Toggle torch on a video track
generatePhotoFilename, // Generate timestamped filename for photos
getPhotoQuality, // Get quality value for a given tier
} from "@formvu/react-media-kit";Development
Prerequisites
- Node.js >= 22
- pnpm (
corepack enableto use the version frompackageManagerfield)
Setup
git clone https://github.com/formvu/react-media-kit.git
cd react-media-kit
pnpm installCommands
| Command | Description |
| -------------------- | ---------------------------------------------- |
| pnpm dev | Start dev server (Vite, accessible via LAN) |
| pnpm build | Build ESM + IIFE bundles and type declarations |
| pnpm test | Run tests (Vitest, watch mode) |
| pnpm vitest run | Run tests once |
| pnpm test:coverage | Run tests with coverage report |
| pnpm lint | Lint TypeScript files |
| pnpm lint:fix | Lint and auto-fix |
| pnpm fm:check | Check formatting (Prettier) |
| pnpm fm:fix | Auto-format code |
| pnpm fix:all | Lint fix + format in one command |
Build Output
build/dist/
├── react-media-kit.js # ESM bundle
├── react-media-kit.iife.js # IIFE bundle (direct browser usage)
└── index.d.ts # Bundled type declarationsProject Structure
src/
├── recorders/
│ ├── livekit/ # LiveKit SFU recorder
│ ├── red5/ # Red5 Pro recorder
│ ├── cloudflare/ # Cloudflare Stream (WHIP) recorder
│ ├── local/ # Browser MediaRecorder
│ └── shared/ # Base types, utilities, error model
├── capturers/
│ ├── photo/ # Single photo capture
│ └── recording-photo/ # Photo capture during recording
├── media-stream/ # MediaStream provider, context, utilities
├── monitors/
│ └── device/ # CPU pressure, frame drops, device tier
├── hooks/ # Shared React hooks
├── lib/ # WebRTC issue detection interceptors
├── utils/ # General utilities
└── index.ts # Public API — all exportsContributing
Making Changes
Create a feature branch from
main:git checkout -b feat/my-featureMake your changes and commit using conventional commits:
git commit -m "feat: add support for screen sharing"Prefixes:
feat:,fix:,docs:,chore:,refactor:,test:,perf:Create a changeset describing what changed:
pnpm changesetSelect the semver bump type:
- patch — bug fixes, internal changes
- minor — new features, non-breaking additions
- major — breaking API changes
Write a short description. This generates a
.changeset/*.mdfile — commit it with your PR.Push and open a PR against
main:git push -u origin feat/my-feature
Code Quality
Pre-commit hooks (Husky + lint-staged) run linting automatically. CI will also check lint, formatting, build, and tests on every PR.
CI/CD
Pull Request — CI Pipeline
When a PR is opened against main, the CI workflow runs:
- Lint — ESLint check
- Format — Prettier check
- Build — Full production build
- Test — Vitest run
If the PR includes a changeset file, CI also:
- Snapshot publish — publishes a pre-release version to npm
- PR comment — posts the install command on the PR
To test a PR before merge:
pnpm add @formvu/react-media-kit@pr-{PR_NUMBER}Merge to Main — Release Pipeline
When a PR is merged into main, the release workflow runs:
- If changeset files exist — creates a "Version Packages" PR that bumps the version in
package.jsonand updatesCHANGELOG.md - If no changeset files exist (i.e., the "Version Packages" PR itself is merged) — builds the package, publishes to npm, and creates a GitHub Release with tag
vX.Y.Z
Release Flow Summary
code changes + changeset → PR → merge → "Version Packages" PR → merge → npm publish + GitHub ReleaseRequired Secrets
| Secret | Description |
| ----------- | ------------------------------------------------------------ |
| NPM_TOKEN | npm access token with publish rights for the @formvu scope |
Set in GitHub repo Settings > Secrets > Actions.
Browser Support
- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+
The Compute Pressure API (useDeviceMonitor) is currently supported in Chromium-based browsers only. The hook falls back gracefully on unsupported browsers.
License
UNLICENSED
