跳转到内容

错误处理、恢复和日志

SDK 错误不是都用同一种形式:可诊断的引擎问题通常带 Diagnostic,非法 API 参数会抛 TypeError/RangeError/ReferenceError,主动取消通常是 AbortError

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);
}

业务分支使用 codeseverityrecoverable 和结构化 details,不解析 message。Message 可能为了调试改变,也不适合直接当最终中文文案。

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。

适合在条件变化后重试:

  • 临时网络断开;
  • 刷新授权后重新读取素材;
  • 清理 quota 或释放资源后重建 Sink;
  • Remote Provider 返回明确可重试的服务错误;
  • GPU 丢失后重建 Session 并降低质量。

不应原样重试:

  • Project Schema 或引用无效;
  • REVISION_CONFLICT
  • codec 配置不支持;
  • Material integrity 不匹配;
  • 用户永久拒绝权限。

重试必须有次数上限、退避、AbortSignal,并在开始前重新获取输入和 preflight。对同一个 closed Sink 重试没有意义。

不能从任意 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 发送同一个身份,不能把另一份 结果冒充为本任务。

命令基于旧 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,那可能覆盖另一项刚完成的编辑。

WebGL context lost、WebGPU device lost 或音频运行时进入不可恢复状态时,产品可以:

  1. 停止接收新的编辑手势和播放请求;
  2. 保留最近成功保存的 Project snapshot 和播放头;
  3. dispose Preview 和 Session;
  4. 必要时改用 WebGL2、draft 质量或更低 DPR;
  5. 创建新 Session,重新 load Project;
  6. 连接 Preview,seek 到原播放头;
  7. 记录恢复是否成功,并把质量变化告诉用户。

不要尝试继续使用已经 disposed 或 device-lost 的内部对象。

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 原样逐帧上传,先聚合。

  • project-changed 后 debounce 保存 Project;
  • 保存 remote provider job ID 和业务任务 ID;
  • 页面卸载前不能依赖长时间异步 dispose,一般只做同步停止并依靠下一次启动清理;
  • 启动时扫描 OPFS 中的半成品和过期任务;
  • Remote Export 用 idempotency key 查询已有任务;
  • 普通本地 Job 不能跨刷新恢复;需要恢复的 MP4/WebM 必须显式使用 exportResumableMuxed(),不能复用已失败 Job 或旧 Sink。

事件字段见事件与统计,错误码见 Diagnostic Codes

浏览器页面不是可靠的长期后台服务。持久化可靠状态(按 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 不能与交互预览争抢无界资源。