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

@libshub/camio

v0.1.2

Published

web camera streaming library

Readme

camio

基于 User Media Stream API 的 Web 摄像头库,支持流获取、抓帧、连续抓帧、定时拍照、录制与 IndexedDB 相册存储。兼容 Web、WebView、微信内置浏览器。

预览、拍照、录制统一走 Canvas 管线(镜像 / 旋转 / 透明度 / 画幅一致),内部用隐藏 video 解码,页面只需绑定预览 canvas。

安装

npm install @libshub/camio

快速开始

import { Camio, CamioErrorCode } from '@libshub/camio';

const camera = new Camio({
  facing: 'user',
  width: 1280,
  height: 720,
  mirror: true,
  onState: (snapshot) => console.log(snapshot.state),
  onError: (error) => console.error(error.code, error.message),
});

const canvas = document.querySelector('canvas')!;
camera.bindPreviewCanvas(canvas);

await camera.start();

const frame = await camera.captureFrame({ format: 'blob' });
const photo = await camera.takePhoto();

camera.startRecording();
const video = await camera.stopRecording();

await camera.stop();
await camera.destroy();

React 示例:

<canvas ref={(el) => camera.bindPreviewCanvas(el)} />

配置参数

构造 Camio 或调用 setConfig() 时可用:

| 参数 | 类型 | 默认值 | 说明 | |------|------|--------|------| | mode | 'default' \| 'worker' | 'default' | 抓帧模式。default 主线程 Canvas;worker 使用 Worker + OffscreenCanvas(仅 blob 且无 scale/crop;否则回退主线程) | | facing | 'user' \| 'environment' | 'user' | 前置 / 后置摄像头 | | width | number | - | 采集宽度(ideal 约束) | | height | number | - | 采集高度(ideal 约束) | | sizePercent | number | 100 | 显示尺寸百分比(0–100),仅影响预览展示缩放;0 时停止预览绘制,拍照 / 录制仍可用 | | aspectRatio | number | 16/9 | 宽高比(预览与输出按 cover 裁剪) | | rotation | 0 \| 90 \| 180 \| 270 | 0 | 旋转角度。90 / 270 时输出宽高对调 | | opacity | number | 1 | 透明度 0–1(预览 / 拍照 / 录制均生效) | | mirror | boolean | 前置默认 true | 是否镜像(预览 / 拍照 / 录制均生效) | | frameRate | number | - | 帧率(ideal 约束;录制 captureStream 也会参考) | | maxFrameRate | number | 0 | 限制预览/录制绘制最高帧率;0 表示不限制(1–120) | | filter | CameraFilter | 'none' | 预览 / 拍照 / 录制共用滤镜:none / grayscale / sepia / invert / blur / brightness / contrast / saturate / hue / warm / cool | | deviceId | string | - | 指定摄像头设备 ID | | audioEnabled | boolean | true | 是否采集麦克风(录制视频是否带声) | | audioDeviceId | string | - | 指定麦克风设备 ID | | audioMeter | boolean \| { enabled?, interval? } | true | 实时麦克风电平(主线程 AnalyserNode,非 Worker)。false 关闭;interval 为回调间隔 ms | | backgroundBlur | boolean | false | 浏览器原生背景虚化(依赖 OS/设备能力;不支持时忽略) | | torch | boolean | false | 手电筒开关;启动后可通过 setConfig / setTorch 热更新 | | zoom | number | - | 视频轨变焦;启动后可通过 setConfig / setZoom 热更新 | | watermark | false \| { text, position?, opacity?, fontSize? } | false | 预览 / 拍照 / 录制水印 | | recording | { mimeType?, videoBitsPerSecond?, maxDuration? } | { maxDuration: 0 } | 录制参数;maxDuration 为 ms,0 不限制 | | photoFormat | 'jpeg' \| 'png' | 'jpeg' | 拍照格式 | | photoQuality | number | 0.92 | JPEG 质量 0–1 | | frameCapture | FrameCaptureSettings | 见下 | 连续抓帧默认配置(可 autoStart) | | videoElement | HTMLVideoElement | - | 可选,仅作解码源;预览请用 bindPreviewCanvas | | debug | boolean | false | 开启后通过 onLog 输出诊断事件 | | photoStore | PhotoStoreOptions \| false | 启用 | 传 false 关闭 IndexedDB 相册 | | onState | (snapshot) => void | - | 状态 / 快照变化回调 | | onError | (error) => void | - | 错误回调 | | onFrame | (frame) => void | - | 抓帧回调 | | onAudioLevel | (level: number) => void | - | 麦克风电平 0–1(需 audioMeter 开启且有音频轨) | | onLog | (entry) => void | - | 诊断日志回调 |

FrameCaptureSettings

| 参数 | 类型 | 默认值 | 说明 | |------|------|--------|------| | autoStart | boolean | false | 摄像头进入 active 后自动开始连续抓帧 | | interval | number | 100 | 抓帧间隔(ms,最小 16) | | scale | number | 1 | 输出缩放 0–1 | | crop | { x, y, w, h } | - | 像素 ROI 裁剪区域 | | format | 'blob' \| 'imageBitmap' \| 'dataUrl' | 'blob' | 连续抓帧输出格式 |

PhotoStoreOptions

| 参数 | 类型 | 默认值 | 说明 | |------|------|--------|------| | dbName | string | 'camio-photo-store' | IndexedDB 库名 | | storeName | string | 'photos' | 对象仓库名 | | maxItems | number | 200 | 最大保存条数,超出自动清理旧数据 |

实例方法

| 方法 | 说明 | |------|------| | bindPreviewCanvas(canvas \| null) | 绑定预览 canvas;库内统一绘制(ResizeObserver 跟随元素尺寸) | | getPreviewCanvas() | 获取当前绑定的预览 canvas | | start() | 请求权限并启动摄像头。失败时进入 error 状态,并抛出 CamioError。已在运行时可传 { force: true } 强制按当前配置重建 stream | | stop() | 停止采集;若正在录制会先结束录制,可能返回视频 MediaRecord | | pause() | 暂停预览绘制与解码(状态变为 paused)。非 active 时抛错 | | resume() | 恢复。非 paused 时抛错 | | destroy() | 释放资源、关闭存储连接 | | setConfig(patch) | 更新配置。修改 facing / 分辨率 / 音频等会重启 stream;镜像 / 旋转等会同步到预览与录制 | | getState() | 获取当前状态快照 | | getStream() | 获取原始 MediaStream,未启动时返回 null | | getComputedSize() | 获取采集 / 输出尺寸(含旋转对调) | | getDisplaySize() | 获取显示尺寸(采集尺寸 × sizePercent) | | getFps() | 近 1 秒内实际预览/录制绘制帧率;循环未运行时为 0 | | getTrackCapabilities() | 获取当前视频轨的 torch / zoom / 对焦能力 | | setTorch(enabled) | 开关手电筒(设备支持时生效) | | setZoom(zoom) | 设置视频轨变焦 | | setFocusPoint(x, y) | 设置归一化 0–1 对焦点 | | refreshDevices() | 主动枚举设备并触发 devices 事件 | | captureFrame(options?) | 抓取单帧 | | startFrameCapture(options?) | 按间隔连续抓帧(高频帧流) | | stopFrameCapture() | 停止连续抓帧 | | startTimedCapture(options?) | 定时拍照 / 抓帧(可写入相册) | | stopTimedCapture() | 停止定时拍照 | | takePhoto(options?) | 拍照并写入 IndexedDB;支持 countdown 秒倒计时 | | startRecording(options?) | 开始录制(canvas captureStream + MediaRecorder),可覆盖 recording 配置 | | pauseRecording() / resumeRecording() | 暂停 / 恢复 MediaRecorder 录制 | | stopRecording() | 结束录制并写入 IndexedDB,返回视频 MediaRecord | | getPhotoHistory(limit?) | 读取相册列表(按时间倒序)。存储关闭时抛错 | | deletePhoto(id) | 删除单条媒体 | | updateMedia(id, patch) | 更新媒体名称 / 备注 | | clearPhotos() | 清空相册 | | on(event, handler) | 订阅事件,返回取消订阅函数 | | off(event, handler) | 取消订阅 | | Camio.listDevices() | 静态方法:枚举摄像头 / 麦克风 | | Camio.checkSupport() | 静态方法:返回媒体、Worker、录制 MIME 与平台支持信息 |

Canvas 管线说明

| 能力 | 实现 | 受哪些配置影响 | |------|------|----------------| | 预览 | FramePipeline 绘制到绑定的 canvas | mirror / rotation / opacity / filter / aspectRatiosizePercent 控制是否绘制与 CSS 缩放;maxFrameRate 限制绘制频率 | | 拍照 / 抓帧 | 临时 canvas(或 Worker OffscreenCanvas) | 同上,含 watermark;输出尺寸随 rotation 90/270 对调;另支持 scale / crop(像素 ROI) | | 录制 | 隐藏 canvas → captureStream,与预览共用帧循环 | 同上(不含 sizePercent);maxFrameRate 优先于 frameRate 作为 captureStream 帧率 |

性能要点:

  • 预览与录制共用一个帧循环,优先 requestVideoFrameCallback
  • sizePercent === 0 或未绑定预览 canvas 且未录制时,循环停止
  • 拍照为按需单次绘制,不进入预览循环

captureFrame / takePhoto 参数

| 参数 | 类型 | 默认值 | 说明 | |------|------|--------|------| | format | 'blob' \| 'imageBitmap' \| 'dataUrl' | 'blob' | 输出格式 | | mimeType | string | 随 photoFormat | 图片 MIME | | quality | number | photoQuality | JPEG 质量 0–1 | | emitFrame | boolean | true | 是否触发 frame 事件(takePhoto 内部为 false) | | scale | number | 1 | 输出缩放 | | crop | { x, y, w, h } | - | 像素 ROI 裁剪(相对抓帧输出画布) |

startFrameCapture 参数

继承 captureFrame 参数,并额外支持:

| 参数 | 类型 | 默认值 | 说明 | |------|------|--------|------| | interval | number | 配置中的 frameCapture.interval | 间隔 ms(≥ 16) | | onFrame | (frame) => void | - | 每帧回调 |

startTimedCapture 参数

继承 captureFrame 参数,并额外支持:

| 参数 | 类型 | 默认值 | 说明 | |------|------|--------|------| | interval | number | 1000 | 间隔 ms(≥ 100) | | count | number | - | 张数上限,达到后自动停止 | | saveToStore | boolean | true | true 时调用 takePhoto 写入相册;false 时只抓帧 | | onPhoto | (photo) => void | - | 每张照片回调(saveToStore: true) | | onFrame | (frame) => void | - | 每帧回调(saveToStore: false) |

状态与事件

CameraState

idlerequestingactivepausedstopped / error

事件

| 事件 | payload | 说明 | |------|----------|------| | state | CameraStateSnapshot | 状态机变化,或快照字段变化(录制中、连拍计数等) | | error | { code, message } | 运行错误 | | frame | CapturedFrame | 抓帧完成 | | audioLevel | number | 麦克风电平 0–1(高频,不走诊断日志) | | devices | DeviceListResult | 设备热插拔或 refreshDevices() 后触发 |

命令式 API 失败时:抛出 CamioError,并触发 error 事件(两者都会发生)。

CameraStateSnapshot 主要字段

  • state:当前状态
  • config:完整配置
  • computedSize:采集 / 输出尺寸(含旋转对调)
  • displaySize:显示尺寸
  • platform'web' \| 'webview' \| 'wechat'
  • hasStream:是否有 stream
  • isRecording:是否正在录制
  • isTimedCapturing / timedCapture:定时拍照状态
  • isFrameCapturing / frameCaptureState:连续抓帧状态
  • adapterReady:模式适配器是否就绪
  • hasAudioTrack:当前 stream 是否有可用音频轨
  • audioLevel:实时麦克风电平 0–1(未开启 meter 时为 null
  • backgroundBlurSupported / backgroundBlurControllable / backgroundBlurActive:原生背景虚化能力与实际状态
  • trackCapabilities:当前视频轨手电筒、变焦、对焦能力
  • countdown:拍照倒计时秒数;无倒计时时为 null
  • isRecordingPaused / recordingElapsedMs / recordingMaxDuration:录制暂停、已录时长与最大时长
  • error:最近错误

录制得到的 MediaRecordhasAudio?: boolean,表示该视频是否带声。

错误码

通过 CamioErrorCode 导出:

| 错误码 | 说明 | |--------|------| | NOT_SUPPORTED | 环境不支持(API / Worker / MediaRecorder 等) | | PERMISSION_DENIED | 用户拒绝摄像头权限 | | DEVICE_NOT_FOUND | 未找到摄像头 | | DEVICE_IN_USE | 设备被占用 | | CONSTRAINT_NOT_SATISFIED | 约束无法满足 | | NOT_ACTIVE | 摄像头未处于可用状态 | | CAPTURE_FAILED | 抓帧失败 | | WORKER_INIT_FAILED | Worker 模式初始化失败 | | STORAGE_UNAVAILABLE | IndexedDB 不可用或已关闭 | | STORAGE_FAILED | 存储读写失败 | | INVALID_CONFIG | 配置参数无效 | | RECORDING_FAILED | 录制失败 | | UNKNOWN | 未知错误 |

捕获示例:

import { CamioError, CamioErrorCode } from '@libshub/camio';

try {
  await camera.start();
} catch (error) {
  if (error instanceof CamioError && error.code === CamioErrorCode.PERMISSION_DENIED) {
    // 处理权限拒绝
  }
}

模式扩展

内置 default / worker 两种模式,可通过 registerModeAdapter 注册自定义模式:

import { registerModeAdapter } from '@libshub/camio';

registerModeAdapter('custom', () => new MyAdapter());

工具函数

| 函数 | 说明 | |------|------| | detectPlatform() | 检测运行环境 | | isMediaDevicesSupported() | 是否支持 getUserMedia | | isWorkerSupported() | 是否支持 Worker 模式 | | enumerateDevices() | 枚举摄像头 / 麦克风(与 Camio.listDevices 相同) | | computeCaptureSize() | 计算采集 / 输出尺寸(含旋转对调) | | computeDisplaySize() | 计算显示尺寸 | | drawVideoFrame() | 按配置将一帧绘制到 canvas(高级用法) | | DEFAULT_ASPECT_RATIO / ASPECT_RATIO_OPTIONS / getPreviewAspectRatio() | 宽高比常量与预览容器计算 |

本地示例

npm run dev          # 启动示例(HTTP)
npm run dev:https    # HTTPS,适合移动端 / 微信调试
npm run build        # 构建库

示例包含 PC(/desktop)与移动端(/mobile)页面,使用 React + Zustand 演示 bindPreviewCanvas 集成方式。

注意事项

  • 摄像头 API 需要 HTTPS 或 localhost
  • 页面预览请绑定 canvas,无需再挂可见 <video>
  • sizePercent 只控制预览是否绘制与展示缩放,不影响拍照 / 录制分辨率
  • mirror / rotation / opacity / aspectRatio 对预览、拍照、录制一致生效
  • 修改 facing、分辨率、音频等采集参数会自动重启摄像头
  • Worker 模式:纯 blob 抓帧走 OffscreenCanvas;带 scale / crop 或非 blob 格式时回退主线程