# web-va-sdk 接入说明 `web-va-sdk` 是不包含 UI 的浏览器音视频素材接入层,负责: - 探测 `File`、`Blob` 或 URL 素材并生成工程 `Asset` - 在 Worker 中生成低分辨率代理、缩略图、关键帧、波形和 OPFS manifest - 先注册原素材预览源,代理完成后自动切换为 OPFS 预览源 - 暴露导入、OPFS、预览 action,以及 `asset.add`、`clip.add` 命令构造函数 - 统一管理队列、取消、Blob URL 和 Worker 生命周期 SDK 不包含 React 组件、样式、Redux store 或具体预览 UI。 ## 安装 当前仓库通过 pnpm workspace 使用: ```json { "dependencies": { "web-va-sdk": "workspace:*" } } ``` 然后执行: ```bash pnpm install ``` 运行环境需要支持 Web Worker、OPFS 和 WebCodecs。页面应运行在 HTTPS 或 localhost 安全上下文中。默认 Worker 入口由 SDK 创建;非 Vite/Rollup 构建环境可通过 `workerFactory` 注入自己的 Worker。 ## 最小接入 ```ts import { WebVASdk, createAddMediaClipCommand, createImportAssetCommand, } from "web-va-sdk"; const sdk = new WebVASdk(); const unsubscribe = sdk.subscribe(() => { const state = sdk.getSnapshot(); renderImportProgress(state.entries); }); const file = fileInput.files?.[0]; if (!file) { throw new Error("请选择素材"); } const entry = await sdk.importAsset({ blob: file, kind: "file", lastModified: file.lastModified, name: file.name, }); if (!entry.asset) { throw new Error("素材探测未生成 Asset"); } project = commandBus.execute(createImportAssetCommand(project, entry.asset)); project = commandBus.execute( createAddMediaClipCommand(project, entry.asset.id, { kind: "video", placement: "playhead", playheadUs, targetTrackId, }), ); preview.update({ audioSources: sdk.getAudioSources(), project, sources: sdk.getPreviewSources(), }); // 页面卸载时释放 Worker、队列和 SDK 创建的 Blob URL。 unsubscribe(); sdk.dispose(); ``` `importAsset` 默认在探测完成后立即: 1. 用原素材注册预览和音频 source fallback; 2. 在后台调用 `buildOpfs`; 3. OPFS 代理完成后,将同一 `assetId` 的预览源切换为 `opfs-proxy`。 因此不需要等待代理完成就可以把 clip 加入工程。 ## 显式 action 需要接入现有 action dispatcher 时,可使用可序列化 action creator: ```ts import { WebVASdk, webVAActions } from "web-va-sdk"; const sdk = new WebVASdk(); await sdk.dispatch( webVAActions.importAsset(source, { autoAddToPreview: false, autoBuildOpfs: false, }), ); const entry = sdk.getSnapshot().entries[0]; if (!entry?.asset) { throw new Error("素材导入失败"); } const entryId = entry.id; const assetId = entry.asset.id; await sdk.dispatch(webVAActions.addToPreview(entryId)); await sdk.dispatch(webVAActions.buildOpfs(entryId)); await sdk.dispatch(webVAActions.removeFromPreview(assetId)); await sdk.dispatch(webVAActions.clearOpfsCache()); ``` 业务代码直接调用 `importAsset`、`buildOpfs`、`addToPreview` 等强类型方法时,返回类型更 精确;`dispatch` 适合统一 action 管线。 ## 数据结构 `WebVAState` 是只读快照: | 字段 | 说明 | | ---------------- | ----------------------------------------------------------- | | `entries` | 每次导入的探测结果、工程 Asset、代理状态及当前 runtime source | | `importQueue` | 探测队列 active、queued、peak、backpressure 指标 | | `proxyQueue` | OPFS 代理队列指标 | | `cacheStats` | OPFS committed/temp 字节、条目数及浏览器存储估算 | | `previewSources` | 已注册到预览的 `assetId -> WebVARuntimeSource` | 单个 `WebVAAssetEntry` 的关键状态: ```ts type WebVAAssetEntry = { id: string; // 本次导入 entry id status: "probing" | "ready" | "failed"; source: BrowserMediaSource; asset?: Asset; result?: MediaProbeResult; runtimeSource?: WebVARuntimeSource; proxy: { status: "idle" | "queued" | "running" | "ready" | "failed" | "cancelled"; ratio: number; progress?: MediaProxyProgress; result?: MediaProxyResult; error?: string; }; }; ``` `runtimeSource.kind` 为 `source` 或 `opfs-proxy`。前者让素材探测后立即可用,后者读取 OPFS 中的代理 MP4,并携带 manifest 和关键帧索引。 ## React 订阅 SDK 的 `subscribe/getSnapshot` 兼容 `useSyncExternalStore`: ```tsx const state = useSyncExternalStore( sdk.subscribe, sdk.getSnapshot, sdk.getSnapshot, ); ``` 组件只消费状态并触发 SDK action,不应自行创建 `WorkerClient`、OPFS 目录或 Blob URL。 ## 手动控制代理 ```ts const entry = await sdk.importAsset(source, { autoBuildOpfs: false, }); await sdk.buildOpfs(entry.id, { parameters: { frameRate: 15, maxHeight: 540, maxWidth: 960, }, }); sdk.cancelOpfs(entry.id); const stats = await sdk.getOpfsCacheStats(); await sdk.clearOpfsCache(); ``` 长于 10 分钟的素材默认使用 15fps、更稀疏缩略图的代理参数。显式 `parameters` 会覆盖 这组 SDK 默认值。 ## 自定义 Worker 构建工具无法处理包内 `new URL(..., import.meta.url)` 时,可在宿主项目创建 Worker bridge: ```ts // src/web-va.worker.ts import "web-va-sdk/worker"; ``` ```ts // 应用初始化 const sdk = new WebVASdk({ workerFactory: () => new Worker(new URL("./web-va.worker.ts", import.meta.url), { name: "project-media-worker", type: "module", }), }); ``` 测试环境也可注入实现 `WorkerTransport` 的内存 transport,不需要真实 OPFS。 ## 工程与预览边界 - `createImportAssetCommand(project, asset)` 只生成 `asset.add`,由宿主 Command Bus 提交。 - `createAddMediaClipCommand(project, assetId, options)` 生成 `clip.add`,支持视频/音频、 播放头插入、目标轨尾追加和指定目标轨。 - `addToPreview(entryId)` 只注册 runtime source,不修改工程文档。 - `getOriginalSources()` 保留原素材引用,导出应继续使用原素材而不是代理。 - `clearOpfsCache()` 会取消导入/代理任务、清空 SDK entries 和预览注册。 - `dispose()` 必须在宿主销毁时调用;调用后 SDK 不可复用。 ## 源码位置 - 公共入口:`packages/web-va-sdk/src/index.ts` - SDK 控制器:`packages/web-va-sdk/src/web-va-sdk.ts` - 数据结构:`packages/web-va-sdk/src/types.ts` - action creator:`packages/web-va-sdk/src/actions.ts` - 工程命令:`packages/web-va-sdk/src/project-actions.ts` - Worker 入口:`packages/web-va-sdk/src/media.worker.ts`