npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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-kit
npm install @formvu/react-media-kit
yarn add @formvu/react-media-kit

Requirements

  • 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 → connected

Common 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 enable to use the version from packageManager field)

Setup

git clone https://github.com/formvu/react-media-kit.git
cd react-media-kit
pnpm install

Commands

| 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 declarations

Project 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 exports

Contributing

Making Changes

  1. Create a feature branch from main:

    git checkout -b feat/my-feature
  2. Make your changes and commit using conventional commits:

    git commit -m "feat: add support for screen sharing"

    Prefixes: feat:, fix:, docs:, chore:, refactor:, test:, perf:

  3. Create a changeset describing what changed:

    pnpm changeset

    Select 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/*.md file — commit it with your PR.

  4. 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:

  1. Lint — ESLint check
  2. Format — Prettier check
  3. Build — Full production build
  4. Test — Vitest run

If the PR includes a changeset file, CI also:

  1. Snapshot publish — publishes a pre-release version to npm
  2. 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:

  1. If changeset files exist — creates a "Version Packages" PR that bumps the version in package.json and updates CHANGELOG.md
  2. 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 Release

Required 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