@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 / aspectRatio;sizePercent 控制是否绘制与 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
idle → requesting → active ↔ paused → stopped / 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:是否有 streamisRecording:是否正在录制isTimedCapturing/timedCapture:定时拍照状态isFrameCapturing/frameCaptureState:连续抓帧状态adapterReady:模式适配器是否就绪hasAudioTrack:当前 stream 是否有可用音频轨audioLevel:实时麦克风电平 0–1(未开启 meter 时为null)backgroundBlurSupported/backgroundBlurControllable/backgroundBlurActive:原生背景虚化能力与实际状态trackCapabilities:当前视频轨手电筒、变焦、对焦能力countdown:拍照倒计时秒数;无倒计时时为nullisRecordingPaused/recordingElapsedMs/recordingMaxDuration:录制暂停、已录时长与最大时长error:最近错误
录制得到的 MediaRecord 含 hasAudio?: 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 格式时回退主线程
