错误处理、恢复和日志
SDK 错误不是都用同一种形式:可诊断的引擎问题通常带 Diagnostic,非法 API 参数会抛 TypeError/RangeError/ReferenceError,主动取消通常是 AbortError。
统一错误入口
Section titled “统一错误入口”import { AelionError } from '@aelionsdk/core';
function handleSdkError(error: unknown): void { if (error instanceof DOMException && error.name === 'AbortError') { return; // 用户取消或请求被新请求取代,不弹红色错误 }
if (error instanceof AelionError) { for (const diagnostic of error.diagnostics) { showDiagnostic(diagnostic); } return; }
if ( error instanceof TypeError || error instanceof RangeError || error instanceof ReferenceError ) { reportProgrammingOrInputError(error); return; }
reportUnknownError(error);}业务分支使用 code、severity、recoverable 和结构化 details,不解析 message。Message 可能为了调试改变,也不适合直接当最终中文文案。
订阅 Session Diagnostic
Section titled “订阅 Session Diagnostic”const unsubscribe = session.subscribe('diagnostic', ({ diagnostic }) => { diagnosticPanel.append(diagnostic);
telemetry.record({ code: diagnostic.code, severity: diagnostic.severity, recoverable: diagnostic.recoverable, entityId: diagnostic.entityId, rangeUs: diagnostic.rangeUs, });});遥测不要上传完整 Project、素材名、URL query、token 或原始 cause。Details 也要按字段 allowlist 过滤,并限制单条和单会话体积。
需要一次性提交可比较的支持包时,使用 Session 的版本化诊断报告:
const report = session.createDiagnosticReport(); // privacy: 'safe'uploadSupportBundle(report);默认 safe 模式只包含稳定错误码、聚合耗时/资源计数、能力状态和非识别性环境位;
它不包含诊断 message/details、实体 ID、浏览器 UA、平台、origin、GPU adapter 或原始
cause。只有用户明确同意且上传链路受控时才使用
createDiagnosticReport({ privacy: 'full' })。自定义 Media Provider 的
getDiagnosticSnapshot() 也必须只返回计数和枚举,不能返回素材 URL、文件名或 token。
哪些错误可以重试
Section titled “哪些错误可以重试”适合在条件变化后重试:
- 临时网络断开;
- 刷新授权后重新读取素材;
- 清理 quota 或释放资源后重建 Sink;
- Remote Provider 返回明确可重试的服务错误;
- GPU 丢失后重建 Session 并降低质量。
不应原样重试:
- Project Schema 或引用无效;
REVISION_CONFLICT;- codec 配置不支持;
- Material integrity 不匹配;
- 用户永久拒绝权限。
重试必须有次数上限、退避、AbortSignal,并在开始前重新获取输入和 preflight。对同一个 closed Sink 重试没有意义。
跨刷新恢复导出
Section titled “跨刷新恢复导出”不能从任意 container byte offset 盲目续写 MP4/WebM。Aelion 的 resumable muxed 路径把输出编码为完整 WebM cluster 或 fMP4 fragment,并在每个单元原子提交后保存 manifest 和 SHA-256;刷新后从第一个缺失单元继续:
import { exportResumableMuxed, IndexedDbResumableMuxedExportStore } from '@aelionsdk/export';
await exportResumableMuxed({ key: exportJobId, contentId: projectContentHash, profile: 'mp4-h264-aac', store: new IndexedDbResumableMuxedExportStore({ databaseName: 'my-editor-export-checkpoints', }), durationUs, width, height, frameRate, sampleRate, channelCount, renderFrame, renderAudio, sink: freshEmptySeekableStream,});恢复调用必须使用相同 content/configuration identity 和一个新的空 Sink。实现会验证 所有已提交单元,最终按顺序重新组装成片;不得把任意半成品 byte offset 当成 checkpoint。
可独立提交的静帧或业务 upload unit 则使用通用 checkpoint runner:
import { BrowserStorageExportCheckpointStore, runCheckpointedExport } from '@aelionsdk/export';
const store = new BrowserStorageExportCheckpointStore({ namespace: 'my-editor-export',});
await runCheckpointedExport({ key: jobId, contentId, profileId: 'still-png', totalUnits, store, processUnit: uploadOneIdempotentUnit,});checkpoint 只在一个 unit 原子提交后前进;processUnit 必须对
(contentId, profileId, unitIndex) 幂等。新的 store 实例会从 localStorage 读回
最后提交位置。远程链路使用相同思想:manifest content ID、revision、profile 与
idempotency key 绑定结果,重连/重试必须向 Provider 发送同一个身份,不能把另一份
结果冒充为本任务。
Revision 冲突怎么恢复
Section titled “Revision 冲突怎么恢复”命令基于旧 snapshot 时:
try { session.transaction.commands.moveItem(command);} catch (error) { if (hasDiagnosticCode(error, 'REVISION_CONFLICT')) { const latest = session.getSnapshot(); reconcileUserIntent(command, latest); return; } throw error;}reconcileUserIntent 应重新找到 Item、目标轨和吸附位置。不能只把 baseRevision 替换成最新值然后提交旧 startUs,那可能覆盖另一项刚完成的编辑。
GPU 或 Player 运行时失败
Section titled “GPU 或 Player 运行时失败”WebGL context lost、WebGPU device lost 或音频运行时进入不可恢复状态时,产品可以:
- 停止接收新的编辑手势和播放请求;
- 保留最近成功保存的 Project snapshot 和播放头;
- dispose Preview 和 Session;
- 必要时改用 WebGL2、draft 质量或更低 DPR;
- 创建新 Session,重新 load Project;
- 连接 Preview,seek 到原播放头;
- 记录恢复是否成功,并把质量变化告诉用户。
不要尝试继续使用已经 disposed 或 device-lost 的内部对象。
事件和统计如何组合
Section titled “事件和统计如何组合”const unsubscribers = [ session.subscribe('project-loaded', onLoaded), session.subscribe('project-changed', onChanged), session.subscribe('state-changed', onStateChanged), session.subscribe('capability-changed', onCapabilityChanged), session.subscribe('stats-changed', onStatsChanged), session.subscribe('diagnostic', onDiagnostic),];推荐遥测维度:SDK version、浏览器大版本、OS、capability tier、实际 backend、Project 规格、profile 和 diagnostic code。不要把高频 stats 原样逐帧上传,先聚合。
页面刷新和崩溃后恢复什么
Section titled “页面刷新和崩溃后恢复什么”project-changed后 debounce 保存 Project;- 保存 remote provider job ID 和业务任务 ID;
- 页面卸载前不能依赖长时间异步 dispose,一般只做同步停止并依靠下一次启动清理;
- 启动时扫描 OPFS 中的半成品和过期任务;
- Remote Export 用 idempotency key 查询已有任务;
- 普通本地 Job 不能跨刷新恢复;需要恢复的 MP4/WebM 必须显式使用
exportResumableMuxed(),不能复用已失败 Job 或旧 Sink。
事件字段见事件与统计,错误码见 Diagnostic Codes。
长会话操作指南
Section titled “长会话操作指南”浏览器页面不是可靠的长期后台服务。持久化可靠状态(按 revision 排序的 Project 快照、素材 locator、remote job ID、resumable export checkpoint),让页面终止后能从最后一个持久点恢复;绝不持久化运行中的 worker、frame、凭据、object URL 或假定的 decoder 状态。重启恢复证据(reports/baseline/recovery-chromium.json)和 transaction 重启 soak 演示了「canonical checkpoint → 丢弃 → 重新准入 → 续传」的路径。
Service worker 能做什么:缓存静态资源、保持注册存活、提供 shell 页面、拦截 fetch。不能做什么:运行 WebGL2/WebGPU 合成器、执行 AudioWorklet 时钟、持有 VideoDecoder/VideoEncoder——GPU 和媒体编码器状态无法跨页面终止存活。因此后台渲染仍在页面生命周期内进行,并在内存/温控压力下降低交互保真度。
需要跨页面存活的 job 用远程导出 provider 作为持久性上限:durable job ID、按素材授权、进度/取消、结果字节/哈希校验是契约。autosave 和 remote hand-off 不能与交互预览争抢无界资源。