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

@cp949/react-webcam

v1.2.0

Published

React 19 webcam component with controlled state, ref handle, and playback pause/resume

Readme

한국어 README

@cp949/react-webcam

React 19 webcam component library.

  • Supports controlled and uncontrolled ownership for component state.
  • Exposes WebcamHandle for snapshots, option updates, and playback pause/resume.
  • Publishes runtime state such as permission denial, autoplay blocking, and unavailable devices through onStateChange.

For repository-level workflow and development commands, see the root README.

Install

pnpm add @cp949/react-webcam
# npm install @cp949/react-webcam

This package is React 19 only. React 18 and below are not supported.

pnpm add react@^19 react-dom@^19

Basic Usage

import { Webcam } from "@cp949/react-webcam";

export function CameraView() {
  return (
    <div style={{ width: 640, height: "auto" }}>
      <Webcam
        webcamOptions={{ aspectRatio: 16 / 9 }}
        visibleFlipButton
        visibleCameraDirectionButton
      />
    </div>
  );
}

Using fitMode to match a fixed parent container:

<div style={{ width: 640, height: 480 }}>
  <Webcam fitMode="cover" webcamOptions={{ audioEnabled: true }} />
</div>

Disabled State

Use disabled when the webcam should stay mounted but must not request camera access yet.

When disabled={true}:

  • getUserMedia() is not called
  • the component stays in the existing idle flow
  • a built-in text-free placeholder with a soft gradient and camera icon is rendered by default

When disabled becomes false again, the webcam resumes its normal request flow.

Default Placeholder

<Webcam disabled />

Custom Disabled Fallback

Pass disabledFallback to replace the built-in placeholder completely.

<Webcam
  disabled
  disabledFallback={
    <div
      style={{
        position: "absolute",
        inset: 0,
        display: "grid",
        placeItems: "center",
        background: "#111",
        color: "#fff",
      }}
    >
      Camera is disabled
    </div>
  }
/>

Custom Error Fallback

Pass errorFallback to render custom UI for error states such as permission denial, missing cameras, unsupported browsers, or insecure contexts. When passed as a function, it receives the current WebcamDetail so you can branch on errorCode.

<Webcam
  errorFallback={(detail) => (
    <div>
      {detail.errorCode === "device-not-found"
        ? "No camera is connected."
        : "Camera could not be started."}
    </div>
  )}
/>

playback-error is not covered by errorFallback because the stream is still alive and only video playback failed.

Toggle Disabled State

import { useState } from "react";
import { Webcam } from "@cp949/react-webcam";

export function DisabledExample() {
  const [disabled, setDisabled] = useState(true);

  return (
    <>
      <button type="button" onClick={() => setDisabled((prev) => !prev)}>
        Toggle webcam
      </button>

      <div style={{ width: 640, height: "auto" }}>
        <Webcam disabled={disabled} />
      </div>
    </>
  );
}

Observe Runtime State With onStateChange

onStateChange fires whenever WebcamDetail changes. pausePlayback() does not change WebcamDetail, so it does not emit onStateChange. On resumePlayback(), the component may keep the current state, clear playback-error, or publish playback-error again when playback still fails.

import { Webcam, type WebcamDetail } from "@cp949/react-webcam";

export function CameraView() {
  function handleStateChange(detail: WebcamDetail) {
    switch (detail.phase) {
      case "live":
        console.log("stream started:", detail.mediaStream);
        break;
      case "playback-error":
        console.warn("playback blocked:", detail.error);
        break;
      case "denied":
        console.warn("camera permission denied");
        break;
      case "unavailable":
        console.warn("camera unavailable or already in use");
        break;
      case "insecure":
        console.error("camera access requires HTTPS or localhost");
        break;
      case "unsupported":
        console.error("camera access is not supported in this browser");
        break;
      case "error":
        if (detail.errorCode === "track-ended") {
          console.warn("camera track ended, ask the user to restart it");
        } else {
          console.error("camera error:", detail.errorCode, detail.error);
        }
        break;
    }
  }

  return (
    <div style={{ width: 640, height: "auto" }}>
      <Webcam
        webcamOptions={{ aspectRatio: 16 / 9 }}
        onStateChange={handleStateChange}
      />
    </div>
  );
}

State Flow

idle -> requesting -> live
               -> denied
               -> unavailable
               -> unsupported
               -> insecure
               -> error

live -> playback-error
playback-error -> live
live -> error
live -> requesting -> live   (automatic restart after webcamOptions changes)

WebcamDetail is a discriminated union keyed by phase.

function handleDetail(detail: WebcamDetail) {
  if (detail.phase === "live") {
    console.log(detail.mediaStream);
    console.log(detail.constraints);
  } else if (detail.phase === "playback-error") {
    console.warn("playback failed:", detail.error);
    console.log(detail.mediaStream);
  } else if (detail.phase === "denied") {
    console.warn(detail.errorCode);
    console.warn(detail.error);
  } else if (detail.phase === "error") {
    console.error(detail.errorCode, detail.error);
  } else if (detail.phase === "insecure") {
    console.error(detail.errorCode);
  }
}

Ref Handle, WebcamHandle

Use ref to obtain a WebcamHandle for imperative snapshot capture, device inspection, and webcam option updates.

import { useRef } from "react";
import { Webcam, type WebcamHandle } from "@cp949/react-webcam";

export function CameraWithSnapshot() {
  const webcamRef = useRef<WebcamHandle>(null);

  function handleSnapshot() {
    const canvas = webcamRef.current?.snapshotToCanvas();
    if (!canvas) return;

    const imageDataUrl = canvas.toDataURL("image/png");
    console.log(imageDataUrl);

    canvas.toBlob((blob) => {
      if (blob) {
        // upload(blob);
      }
    }, "image/jpeg", 0.9);
  }

  return (
    <div>
      <div style={{ width: 640, height: "auto" }}>
        <Webcam ref={webcamRef} webcamOptions={{ aspectRatio: 16 / 9 }} />
      </div>
      <button type="button" onClick={handleSnapshot}>
        Take snapshot
      </button>
    </div>
  );
}

WebcamHandle API

| Method | Description | | --- | --- | | snapshotToCanvas(options?) | Returns the current frame as HTMLCanvasElement, or null before ready | | getPlayingVideoDeviceId() | Returns the current video track device ID | | getPlayingAudioDeviceId() | Returns the current audio track device ID | | setFlipped(value) | Updates the horizontal flip state | | setWebcamOptions(updater) | Updates webcam options | | pausePlayback() | Pauses only video playback, while keeping the camera stream alive | | resumePlayback() | Resumes paused video playback |

pausePlayback() and resumePlayback() only call video.pause() and video.play(). They do not stop the camera hardware or stop any MediaStreamTrack. The camera LED can remain on, and track-ended detection still runs. pausePlayback() does not emit onStateChange, while resumePlayback() can publish playback-error on failure.

HTMLVideoElement and MediaStream are not exposed directly from the handle. Read stream information from WebcamDetail inside onStateChange.

List Media Devices

Use the public utilities when you need device lists before rendering.

import {
  listAudioInputDevices,
  listMediaDevices,
  listVideoInputDevices,
} from "@cp949/react-webcam";

const allDevices = await listMediaDevices();
const cameras = await listVideoInputDevices();
const microphones = await listAudioInputDevices();

The return type is the browser-native MediaDeviceInfo[].

Labels And Localization

The built-in button labels default to Korean. Pass the labels prop when you want English or app-specific strings.

<Webcam
  visibleFlipButton
  visibleCameraDirectionButton
  visibleAspectRatioButton
  visibleSnapshotButton
  labels={{
    flip: "Mirror",
    cameraDirection: "Front / Rear Camera",
    facingModeBack: "Rear",
    facingModeFront: "Front",
    facingModeDefault: "Default",
    aspectRatio: "Aspect ratio",
    aspectRatioAuto: "Auto",
    snapshot: "Take snapshot",
  }}
/>

State Ownership, Controlled / Uncontrolled

flipped and webcamOptions follow standard React controlled/uncontrolled patterns.

flipped

| Pattern | Behavior | | --- | --- | | flipped + onFlippedChange | Fully controlled. Buttons and ref updates call onFlippedChange only | | flipped | Read-only controlled. Button and ref changes are ignored | | defaultFlipped | Uncontrolled. Initial value only, then internal state is managed by the component | | Neither prop | Uncontrolled, default initial value false |

import { useState } from "react";
import { Webcam } from "@cp949/react-webcam";

export function FlipOwnershipExample() {
  const [flipped, setFlipped] = useState(false);

  return (
    <>
      <Webcam
        flipped={flipped}
        onFlippedChange={setFlipped}
        visibleFlipButton
      />

      <Webcam
        defaultFlipped
        visibleFlipButton
      />
    </>
  );
}

webcamOptions

| Pattern | Behavior | | --- | --- | | webcamOptions + onWebcamOptionsChange | Fully controlled | | webcamOptions | Read-only controlled | | defaultWebcamOptions | Uncontrolled | | Neither prop | Uncontrolled with defaults |

import { useState } from "react";
import { Webcam, type WebcamOptions } from "@cp949/react-webcam";

export function WebcamOptionsOwnershipExample() {
  const [options, setOptions] = useState<WebcamOptions>({
    facingMode: "user",
    aspectRatio: 16 / 9,
  });

  return (
    <>
      <Webcam
        webcamOptions={options}
        onWebcamOptionsChange={setOptions}
        visibleCameraDirectionButton
        visibleAspectRatioButton
      />

      <Webcam
        defaultWebcamOptions={{ facingMode: "environment", aspectRatio: 4 / 3 }}
        visibleCameraDirectionButton
        visibleAspectRatioButton
      />
    </>
  );
}

Runtime Constraints

  • This package only works in browser environments.
  • Camera access requires a secure context, HTTPS or localhost.
  • If no usable device exists, or another app holds the device, the component can enter the unavailable state.
  • Browser autoplay policy can block video.play(), producing the playback-error state. In that case the camera stream remains alive, and you can recover to live after user interaction by calling resumePlayback().

Demo App

apps/demo is not a replacement for the package README. It is a learning and verification app for the public API.

  • Basic Usage
  • Common Controls
  • Controlled State
  • Device Selection
  • Pause / Resume
  • Ref Handle
  • State Inspector
  • Recipes