@cut-workbench/core 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/{canvas-size-Ct61OYjW.cjs → canvas-size-DIABzEW2.cjs} +212 -44
- package/dist/chunks/canvas-size-DIABzEW2.cjs.map +1 -0
- package/dist/chunks/{canvas-size-CM2oKApi.js → canvas-size-Dlnng74w.js} +168 -30
- package/dist/chunks/canvas-size-Dlnng74w.js.map +1 -0
- package/dist/chunks/id-CJsdlGei.js +35 -0
- package/dist/chunks/id-CJsdlGei.js.map +1 -0
- package/dist/chunks/id-DaCxD4-5.cjs +70 -0
- package/dist/chunks/id-DaCxD4-5.cjs.map +1 -0
- package/dist/chunks/{snap-DyIuO2hU.cjs → snap-Cfk15JVg.cjs} +2 -22
- package/dist/chunks/{snap-DyIuO2hU.cjs.map → snap-Cfk15JVg.cjs.map} +1 -1
- package/dist/chunks/{snap-CBV9G4xq.js → snap-DpkVPVJN.js} +3 -17
- package/dist/chunks/{snap-CBV9G4xq.js.map → snap-DpkVPVJN.js.map} +1 -1
- package/dist/chunks/thumbnails-DEjnao5r.cjs +81 -0
- package/dist/chunks/thumbnails-DEjnao5r.cjs.map +1 -0
- package/dist/chunks/thumbnails-qH0dX_Zs.js +64 -0
- package/dist/chunks/thumbnails-qH0dX_Zs.js.map +1 -0
- package/dist/chunks/{time-CnMfSVYX.cjs → time-CYEd91qN.cjs} +1 -69
- package/dist/chunks/time-CYEd91qN.cjs.map +1 -0
- package/dist/chunks/{time-C_gvkVl1.js → time-ChF1GwCy.js} +2 -34
- package/dist/chunks/time-ChF1GwCy.js.map +1 -0
- package/dist/chunks/{waveform-Ie-G5Lle.cjs → waveform-BYwDdKpC.cjs} +34 -15
- package/dist/chunks/waveform-BYwDdKpC.cjs.map +1 -0
- package/dist/chunks/{waveform-DJkRtMct.js → waveform-uyGNE01H.js} +32 -13
- package/dist/chunks/waveform-uyGNE01H.js.map +1 -0
- package/dist/engine/index.cjs +668 -76
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.js +648 -57
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/webav/exporter.d.ts +3 -1
- package/dist/engine/webav/import-files.d.ts +17 -2
- package/dist/engine/webav/import-pipeline.d.ts +10 -0
- package/dist/engine/webav/types.d.ts +15 -0
- package/dist/index.cjs +18 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/lib/error.d.ts +6 -0
- package/dist/media/thumbnails.d.ts +8 -0
- package/dist/project/schema.d.ts +16 -4
- package/dist/timeline/index.cjs +10 -6
- package/dist/timeline/index.d.ts +1 -0
- package/dist/timeline/index.js +4 -4
- package/dist/timeline/normalize.d.ts +28 -0
- package/dist/timeline/ops.d.ts +0 -4
- package/dist/timeline/snap.d.ts +0 -2
- package/dist/types.d.ts +14 -2
- package/package.json +1 -1
- package/dist/chunks/canvas-size-CM2oKApi.js.map +0 -1
- package/dist/chunks/canvas-size-Ct61OYjW.cjs.map +0 -1
- package/dist/chunks/import-pipeline-A8KkqtZT.cjs +0 -610
- package/dist/chunks/import-pipeline-A8KkqtZT.cjs.map +0 -1
- package/dist/chunks/import-pipeline-DLIdgrIb.js +0 -521
- package/dist/chunks/import-pipeline-DLIdgrIb.js.map +0 -1
- package/dist/chunks/time-C_gvkVl1.js.map +0 -1
- package/dist/chunks/time-CnMfSVYX.cjs.map +0 -1
- package/dist/chunks/waveform-DJkRtMct.js.map +0 -1
- package/dist/chunks/waveform-Ie-G5Lle.cjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["createTextClip","DEFAULT_TEXT_STYLE","createSubtitleClip","DEFAULT_SUBTITLE_STYLE","createClipFromFile","createWebBridge","errorMessage","createMemoryStorage","createEmptyProject","createEventBus","createHistoryStore","createEditorStore","serializeProject","deserializeProject","getProjectDuration","resolveExportSettings","createExportFilename","ExportCancelledError","detectEnvironment","detectFileType","importFile","UnsupportedFileError","asUs","VisibleSprite","OffscreenSprite","Combinator","clampStillDuration","fixFMP4Duration","loadPcmForAsset","ExportCancelledError","createExportFilename","AVCanvas","pointInTransform","fixFMP4Duration","MP4Clip","ImgClip","EmbedSubtitlesClip","Combinator","OffscreenSprite","fixFMP4Duration"],"sources":["../../src/engine/webav/offset-clip.ts","../../src/engine/webav/clip-registry.ts","../../src/engine/clock/playback-clock.ts","../../src/engine/clock/preview-audio.ts","../../src/editor/runtime.ts","../../src/editor/create-editor.ts","../../src/engine/webav/import-files.ts","../../src/engine/webav/sprite-factory.ts","../../src/engine/webav/gain-clip.ts","../../src/engine/webav/coordinate.ts","../../src/engine/webav/exporter.ts","../../src/engine/webav/export-runner.ts","../../src/engine/webav/sprite-sync.ts","../../src/engine/webav/preview-canvas.ts","../../src/engine/webav/fix-duration.ts","../../src/engine/webav/pcm-clip.ts","../../src/engine/webav/export-worker-runtime.ts"],"sourcesContent":["/**\r\n * 自定义 IClip:给素材加「入点」偏移与时长上限。\r\n *\r\n * 为什么需要:WebAV 的 Sprite 没有 in-point 概念 —— `sprite.tick(time - offset)`\r\n * 永远从 0 开始取数据。时间轴上裁剪、分割后的 clip 需要从源素材的中间某处开始播放,\r\n * 因此用这层包装把时间平移 `inPointUs`。\r\n *\r\n * 单位:全部微秒。\r\n */\r\n\r\nimport type { IClip } from '@webav/av-cliper'\r\nimport type { ClipMetaBase as IClipMeta } from './meta'\r\n\r\nexport interface OffsetClipOptions {\r\n /** 源素材入点(微秒) */\r\n inPointUs: number\r\n /** 该 clip 在时间轴上的可见时长(微秒)。超过后 tick 返回 done */\r\n durationUs: number\r\n}\r\n\r\nexport class OffsetClip implements IClip {\r\n readonly ready: Promise<IClipMeta>\r\n private inner: IClip\r\n private opts: OffsetClipOptions\r\n private metaValue: IClipMeta\r\n private destroyed = false\r\n\r\n constructor(inner: IClip, opts: OffsetClipOptions) {\r\n this.inner = inner\r\n this.opts = opts\r\n this.metaValue = {\r\n width: 0,\r\n height: 0,\r\n duration: opts.durationUs,\r\n }\r\n this.ready = inner.ready.then((meta) => {\r\n this.metaValue = {\r\n width: meta.width,\r\n height: meta.height,\r\n duration: opts.durationUs,\r\n }\r\n return this.metaValue\r\n })\r\n }\r\n\r\n get meta(): IClipMeta {\r\n return this.metaValue\r\n }\r\n\r\n async tick(time: number): Promise<{\r\n video?: VideoFrame | ImageBitmap | null\r\n audio?: Float32Array[]\r\n state: 'done' | 'success'\r\n }> {\r\n if (this.destroyed) return { state: 'done' }\r\n if (time >= this.opts.durationUs) return { audio: [], state: 'done' }\r\n return this.inner.tick(time + this.opts.inPointUs)\r\n }\r\n\r\n async split(time: number): Promise<[this, this]> {\r\n if (time <= 0 || time >= this.opts.durationUs) {\r\n throw new Error('\"time\" out of bounds')\r\n }\r\n const leftInner = await this.inner.clone()\r\n const rightInner = await this.inner.clone()\r\n const left = new OffsetClip(leftInner, {\r\n inPointUs: this.opts.inPointUs,\r\n durationUs: time,\r\n })\r\n const right = new OffsetClip(rightInner, {\r\n inPointUs: this.opts.inPointUs + time,\r\n durationUs: this.opts.durationUs - time,\r\n })\r\n await Promise.all([left.ready, right.ready])\r\n return [left as this, right as this]\r\n }\r\n\r\n async clone(): Promise<this> {\r\n const copy = new OffsetClip(await this.inner.clone(), { ...this.opts })\r\n await copy.ready\r\n return copy as this\r\n }\r\n\r\n destroy(): void {\r\n if (this.destroyed) return\r\n this.destroyed = true\r\n this.inner.destroy()\r\n }\r\n}\r\n","/**\r\n * IClip 运行时注册表。\r\n *\r\n * 职责:\r\n * - 持有每个素材的「母版」IClip 与源 File,用于 clone / 抽帧 / 重建;\r\n * - 为时间轴上的每个 clip 派生独立 IClip 实例(通过 clone 或重新构造);\r\n * - 统一 destroy,避免内存泄漏 —— 大素材下这是最常见的崩溃原因。\r\n *\r\n * 派生实例会被 `OffsetClip` 包裹,从而支持时间轴上的入点(in-point)裁剪。\r\n *\r\n * 本模块不依赖 React;调用方(store / hooks)负责触发。\r\n */\r\n\r\nimport type { IClip } from '@webav/av-cliper'\r\nimport type { MediaAsset } from '../../types'\r\nimport { createClipFromFile, createSubtitleClip, createTextClip } from './clip-factory'\r\nimport { DEFAULT_SUBTITLE_STYLE, DEFAULT_TEXT_STYLE } from './styles'\r\nimport { OffsetClip } from './offset-clip'\r\nimport { releaseThumbnails } from '../../media/thumbnails'\r\n\r\ninterface AssetRuntime {\r\n asset: MediaAsset\r\n file: File | null\r\n /** 母版 clip:文件素材才有 */\r\n master: IClip | null\r\n}\r\n\r\nexport interface CreateInstanceParams {\r\n timelineClipId: string\r\n assetId: string\r\n inPointUs: number\r\n durationUs: number\r\n text?: string\r\n textStyle?: MediaAsset['textStyle']\r\n srt?: string\r\n subtitleStyle?: MediaAsset['subtitleStyle']\r\n projectWidth: number\r\n projectHeight: number\r\n}\r\n\r\nexport class ClipRegistry {\r\n private assets = new Map<string, AssetRuntime>()\r\n private derived = new Map<string, IClip>()\r\n\r\n registerAsset(asset: MediaAsset, file: File | null, master: IClip | null): void {\r\n this.assets.set(asset.id, { asset, file, master })\r\n }\r\n\r\n getAsset(assetId: string): MediaAsset | undefined {\r\n return this.assets.get(assetId)?.asset\r\n }\r\n\r\n getFile(assetId: string): File | null {\r\n return this.assets.get(assetId)?.file ?? null\r\n }\r\n\r\n getMaster(assetId: string): IClip | null {\r\n return this.assets.get(assetId)?.master ?? null\r\n }\r\n\r\n listAssets(): MediaAsset[] {\r\n return Array.from(this.assets.values()).map((r) => r.asset)\r\n }\r\n\r\n /**\r\n * 为非文件素材(文字 / 字幕)重建实例。\r\n * 这两个类型无法 clone(clone 会带走旧的 duration / 内容),必须每次新建。\r\n */\r\n private buildProceduralClip(params: CreateInstanceParams, asset: MediaAsset): IClip {\r\n if (asset.kind === 'text') {\r\n return createTextClip({\r\n text: params.text ?? asset.text ?? '文本',\r\n style: { ...DEFAULT_TEXT_STYLE, ...(asset.textStyle ?? {}), ...(params.textStyle ?? {}) },\r\n width: params.projectWidth,\r\n height: params.projectHeight,\r\n durationUs: params.durationUs,\r\n })\r\n }\r\n return createSubtitleClip({\r\n srt: params.srt ?? asset.srt ?? '',\r\n style: { ...DEFAULT_SUBTITLE_STYLE, ...(asset.subtitleStyle ?? {}), ...(params.subtitleStyle ?? {}) },\r\n width: params.projectWidth,\r\n height: params.projectHeight,\r\n })\r\n }\r\n\r\n /**\r\n * 为时间轴 clip 创建独立 IClip(会被 OffsetClip 包裹以支持入点)。\r\n *\r\n * @throws 素材已不存在或缺少数据源时抛出,调用方应提示用户重新导入\r\n */\r\n async createInstance(params: CreateInstanceParams): Promise<IClip> {\r\n const runtime = this.assets.get(params.assetId)\r\n if (!runtime) throw new Error(`素材已不存在:${params.assetId}`)\r\n\r\n this.destroyInstance(params.timelineClipId)\r\n\r\n const { asset } = runtime\r\n let clip: IClip\r\n\r\n if (asset.kind === 'text' || asset.kind === 'subtitle') {\r\n clip = this.buildProceduralClip(params, asset)\r\n } else if (runtime.master) {\r\n clip = await runtime.master.clone()\r\n } else if (runtime.file) {\r\n // 兜底:母版失效时从文件重建\r\n const created = await createClipFromFile(runtime.file, asset.kind)\r\n clip = created.clip\r\n } else {\r\n throw new Error(`素材缺少可用的数据源:${asset.name}`)\r\n }\r\n\r\n const wrapped =\r\n params.inPointUs > 0\r\n ? new OffsetClip(clip, {\r\n inPointUs: params.inPointUs,\r\n durationUs: params.durationUs,\r\n })\r\n : clip\r\n\r\n this.derived.set(params.timelineClipId, wrapped)\r\n return wrapped\r\n }\r\n\r\n getInstance(timelineClipId: string): IClip | null {\r\n return this.derived.get(timelineClipId) ?? null\r\n }\r\n\r\n destroyInstance(timelineClipId: string): void {\r\n const clip = this.derived.get(timelineClipId)\r\n if (!clip) return\r\n try {\r\n clip.destroy()\r\n } catch {\r\n // destroy 失败不应阻断清理流程\r\n }\r\n this.derived.delete(timelineClipId)\r\n }\r\n\r\n /**\r\n * 重新导入同名文件后,把二进制与母版换回原 assetId(id 不变,时间轴立即恢复)。\r\n *\r\n * 注意:旧母版与新文件可能不是同一内容,但工程里记录的 meta 已被新素材覆盖,\r\n * 因此这里直接替换而不是合并。\r\n */\r\n replaceAssetSource(assetId: string, file: File, master: IClip | null): void {\r\n const existing = this.assets.get(assetId)\r\n if (existing == null) return\r\n try {\r\n existing.master?.destroy()\r\n } catch {\r\n // ignore\r\n }\r\n this.assets.set(assetId, { ...existing, file, master })\r\n // 派生实例基于旧母版,全部销毁让上层按新素材重建\r\n this.destroyAllInstances()\r\n }\r\n\r\n /** 移除素材:销毁母版与缩略图 URL(派生实例由时间轴侧负责) */\r\n removeAsset(assetId: string): void {\r\n const runtime = this.assets.get(assetId)\r\n try {\r\n runtime?.master?.destroy()\r\n } catch {\r\n // ignore\r\n }\r\n this.assets.delete(assetId)\r\n releaseThumbnails(assetId)\r\n }\r\n\r\n destroyAllInstances(): void {\r\n for (const [id] of this.derived) {\r\n this.destroyInstance(id)\r\n }\r\n }\r\n\r\n /** 整体销毁(应用卸载) */\r\n dispose(): void {\r\n this.destroyAllInstances()\r\n for (const [assetId, runtime] of this.assets) {\r\n try {\r\n runtime.master?.destroy()\r\n } catch {\r\n // ignore\r\n }\r\n releaseThumbnails(assetId)\r\n }\r\n this.assets.clear()\r\n }\r\n\r\n /** 调试用:当前资源计数 */\r\n stats(): { assets: number; instances: number } {\r\n return { assets: this.assets.size, instances: this.derived.size }\r\n }\r\n}\r\n","/**\r\n * 主播放时钟(框架无关)。\r\n *\r\n * 为什么不用 setInterval 累加:定时器精度差且会累积漂移。\r\n * 为什么不用 AVCanvas.play 当主时钟:它按固定 33ms 步进,且无法与外部音频调度对齐。\r\n *\r\n * 这里以 `AudioContext.currentTime`(硬件音频时钟)为唯一时间源:\r\n *\r\n * currentUs = anchorUs + (ctx.currentTime - anchorCtxTime) * 1e6 * rate\r\n *\r\n * 暂停/跳转/变速时重设锚点,因此不存在累积误差。\r\n */\r\n\r\nexport type PlaybackState = 'paused' | 'playing'\r\n\r\nexport interface ClockAnchor {\r\n /** AudioContext 时间(秒) */\r\n ctxTime: number\r\n /** 对应的工程时间(微秒) */\r\n us: number\r\n rate: number\r\n}\r\n\r\ntype Listener = () => void\r\n\r\nexport class PlaybackClock {\r\n private ctx: AudioContext | null = null\r\n private anchorCtxTime = 0\r\n private anchorUs = 0\r\n private rate = 1\r\n private state: PlaybackState = 'paused'\r\n private listeners = new Set<Listener>()\r\n\r\n /** 惰性创建 AudioContext;必须由用户手势触发 resume,否则浏览器会挂起 */\r\n ensureContext(): AudioContext {\r\n if (this.ctx == null) {\r\n this.ctx = new AudioContext({ latencyHint: 'interactive' })\r\n }\r\n if (this.ctx.state === 'suspended') {\r\n void this.ctx.resume()\r\n }\r\n return this.ctx\r\n }\r\n\r\n get context(): AudioContext | null {\r\n return this.ctx\r\n }\r\n\r\n get isPlaying(): boolean {\r\n return this.state === 'playing'\r\n }\r\n\r\n get playbackRate(): number {\r\n return this.rate\r\n }\r\n\r\n /** 当前工程时间(微秒) */\r\n get currentUs(): number {\r\n if (this.state !== 'playing' || this.ctx == null) return this.anchorUs\r\n const elapsed = this.ctx.currentTime - this.anchorCtxTime\r\n // clamp:标签页切后台后 ctx.currentTime 仍会前进,但不应让播放头越过终点\r\n return Math.max(0, Math.round(this.anchorUs + elapsed * 1_000_000 * this.rate))\r\n }\r\n\r\n /** 当前锚点,音频调度器据此换算 AudioContext 时间 */\r\n get anchor(): ClockAnchor {\r\n return { ctxTime: this.anchorCtxTime, us: this.anchorUs, rate: this.rate }\r\n }\r\n\r\n /** 播放头位置(暂停时也有效) */\r\n get positionUs(): number {\r\n return this.currentUs\r\n }\r\n\r\n play(fromUs: number, rate = this.rate): void {\r\n this.ensureContext()\r\n this.rate = rate > 0 ? rate : 1\r\n this.anchorUs = Math.max(0, fromUs)\r\n this.anchorCtxTime = this.ctx?.currentTime ?? 0\r\n this.state = 'playing'\r\n this.emit()\r\n }\r\n\r\n pause(): void {\r\n if (this.state === 'paused') return\r\n this.anchorUs = this.currentUs\r\n this.state = 'paused'\r\n this.emit()\r\n }\r\n\r\n /** 跳转;播放中跳转会重设锚点并通知订阅者(需要重启音频调度) */\r\n seek(us: number): void {\r\n const next = Math.max(0, Math.round(us))\r\n if (this.state === 'playing' && this.ctx != null) {\r\n this.anchorUs = next\r\n this.anchorCtxTime = this.ctx.currentTime\r\n } else {\r\n this.anchorUs = next\r\n }\r\n this.emit()\r\n }\r\n\r\n setRate(rate: number): void {\r\n const next = rate > 0 ? rate : 1\r\n if (this.state === 'playing' && this.ctx != null) {\r\n // 以当前位置为新锚点,避免速率切换造成时间跳变\r\n this.anchorUs = this.currentUs\r\n this.anchorCtxTime = this.ctx.currentTime\r\n }\r\n this.rate = next\r\n this.emit()\r\n }\r\n\r\n subscribe(listener: Listener): () => void {\r\n this.listeners.add(listener)\r\n return () => this.listeners.delete(listener)\r\n }\r\n\r\n private emit(): void {\r\n for (const l of this.listeners) l()\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n this.listeners.clear()\r\n try {\r\n await this.ctx?.close()\r\n } catch {\r\n // 关闭失败无需处理\r\n }\r\n this.ctx = null\r\n }\r\n}\r\n","/**\r\n * 预览音频引擎。\r\n *\r\n * 为什么需要它:预览画面由 AVCanvas.previewFrame 逐帧驱动(受主时钟控制),\r\n * AVCanvas 自带的播放器会再建一套 33ms 步进的音频调度,两套时钟必然打架。\r\n * 因此这里把音频调度完全接管:按 clip 的 offset/duration 把 PCM 切片排进\r\n * AudioContext 的时间轴,与 PlaybackClock 使用同一个 AudioContext,天然同步。\r\n *\r\n * PCM 来源:对含音轨的素材用 AudioClip 解码一次(内部走 decodeAudioData),\r\n * 之后按 assetId 缓存,避免重复解码。\r\n *\r\n * 音量与淡入淡出通过 GainNode 包络实现;变速使用 AudioBufferSourceNode.playbackRate,\r\n * 与 WebAV 一致地「变调」。\r\n */\r\n\r\nimport type { MediaAsset, TimelineClip, Track } from '../../types'\r\n\r\n/** 预览音频引擎需要的最小 clip 接口(避免 core/clock 直接依赖 @webav) */\r\nexport interface PcmSource {\r\n ready: Promise<{ duration: number; sampleRate?: number; chanCount?: number; width: number; height: number }>\r\n getPCMData(): Float32Array[]\r\n destroy(): void\r\n}\r\n\r\ninterface PcmEntry {\r\n channels: Float32Array[]\r\n sampleRate: number\r\n durationUs: number\r\n /** 原始解码结果的引用,卸载时 destroy */\r\n clip: PcmSource | null\r\n}\r\n\r\ninterface ScheduledSource {\r\n source: AudioBufferSourceNode\r\n gain: GainNode\r\n}\r\n\r\nexport interface AudioEngineClip {\r\n clip: TimelineClip\r\n track: Track\r\n assetKind: MediaAsset['kind']\r\n}\r\n\r\nconst PCM_CACHE_LIMIT = 6\r\n\r\nexport class PreviewAudioEngine {\r\n private ctx: AudioContext | null = null\r\n private pcmCache = new Map<string, Promise<PcmEntry>>()\r\n private scheduled: ScheduledSource[] = []\r\n private masterGain: GainNode | null = null\r\n private masterVolume = 1\r\n\r\n attach(ctx: AudioContext): void {\r\n if (this.ctx === ctx) return\r\n this.stopAll()\r\n this.ctx = ctx\r\n this.masterGain = ctx.createGain()\r\n this.masterGain.gain.value = this.masterVolume\r\n this.masterGain.connect(ctx.destination)\r\n }\r\n\r\n setMasterVolume(volume: number): void {\r\n this.masterVolume = Math.max(0, Math.min(1, volume))\r\n if (this.masterGain) this.masterGain.gain.value = this.masterVolume\r\n }\r\n\r\n /**\r\n * 注册一个素材的 PCM 解码器。\r\n *\r\n * @param load 返回该素材对应的 AudioClip(视频素材也可,MP4 的 AAC 音轨可被 decodeAudioData 解析)\r\n */\r\n registerAsset(assetId: string, load: () => Promise<PcmSource>): void {\r\n if (this.pcmCache.has(assetId)) return\r\n const promise = (async (): Promise<PcmEntry> => {\r\n const clip = await load()\r\n const meta = await clip.ready\r\n return {\r\n channels: clip.getPCMData(),\r\n sampleRate: (meta as { sampleRate?: number }).sampleRate ?? 48000,\r\n durationUs: meta.duration,\r\n clip,\r\n }\r\n })().catch((err: unknown) => {\r\n // 解码失败时移除缓存项,避免后续每次播放都命中一个被 reject 的 Promise。\r\n // 预览音频失败不影响画面播放,因此只记录警告。\r\n console.warn('[preview-audio] 音频解码失败', assetId, err)\r\n this.pcmCache.delete(assetId)\r\n throw err\r\n })\r\n // 该 Promise 主要供 scheduleFrom 内部 await;没有播放请求时无人 await,\r\n // 必须自己标记为「已处理」,否则会变成 unhandled rejection。\r\n promise.catch(() => undefined)\r\n this.pcmCache.set(assetId, promise)\r\n // 简单 LRU:超过上限时淘汰最早的\r\n if (this.pcmCache.size > PCM_CACHE_LIMIT) {\r\n const firstKey = this.pcmCache.keys().next().value\r\n if (firstKey) {\r\n const entry = this.pcmCache.get(firstKey)\r\n this.pcmCache.delete(firstKey)\r\n void entry?.then((e) => e.clip?.destroy()).catch(() => undefined)\r\n }\r\n }\r\n }\r\n\r\n hasAsset(assetId: string): boolean {\r\n return this.pcmCache.has(assetId)\r\n }\r\n\r\n /**\r\n * 从 `fromUs` 开始排程所有可听 clip 的音频。\r\n *\r\n * @param fromUs 播放起点(微秒)\r\n * @param clips 当前时间轴上的全部 clip\r\n * @returns 已排程的结束时间(微秒),用于 clock 自动暂停\r\n */\r\n async scheduleFrom(\r\n fromUs: number,\r\n clips: AudioEngineClip[],\r\n projectEndUs: number,\r\n ): Promise<void> {\r\n this.stopAll()\r\n const ctx = this.ctx\r\n const master = this.masterGain\r\n if (ctx == null || master == null) return\r\n\r\n const audible = clips.filter(\r\n (c) => c.track.kind === 'audio' || c.assetKind === 'video' || c.assetKind === 'audio',\r\n )\r\n if (audible.length === 0) return\r\n\r\n const ctxNow = ctx.currentTime\r\n const startUs = Math.max(0, fromUs)\r\n\r\n for (const item of audible) {\r\n const { clip, track } = item\r\n if (track.muted || track.hidden) continue\r\n if (clip.muted) continue\r\n const clipEnd = clip.startUs + clip.durationUs\r\n if (clipEnd <= startUs) continue\r\n\r\n const entry = await this.getEntry(clip.assetId)\r\n if (entry == null || entry.channels.length === 0) continue\r\n\r\n const playStartUs = Math.max(startUs, clip.startUs)\r\n const fadeOffsetUs = playStartUs - clip.startUs\r\n const sourceStartUs = clip.inPointUs + fadeOffsetUs * clip.playbackRate\r\n const remainingTimelineUs = clipEnd - playStartUs\r\n if (remainingTimelineUs <= 0) continue\r\n\r\n // 源素材可提供的剩余长度(防止越界)\r\n const sourceAvailableUs = Math.max(0, entry.durationUs - sourceStartUs)\r\n const playableTimelineUs = Math.min(\r\n remainingTimelineUs,\r\n sourceAvailableUs / Math.max(0.01, clip.playbackRate),\r\n )\r\n if (playableTimelineUs <= 0) continue\r\n\r\n const startSec = ctxNow + (playStartUs - startUs) / 1_000_000\r\n const buffer = this.sliceBuffer(ctx, entry, sourceStartUs, playableTimelineUs, clip.playbackRate)\r\n if (buffer == null) continue\r\n\r\n const gain = ctx.createGain()\r\n gain.connect(master)\r\n this.applyEnvelope(\r\n gain,\r\n startSec,\r\n playableTimelineUs / 1_000_000,\r\n clip,\r\n fadeOffsetUs,\r\n )\r\n\r\n const source = ctx.createBufferSource()\r\n source.buffer = buffer\r\n source.playbackRate.value = clip.playbackRate\r\n source.connect(gain)\r\n source.start(startSec)\r\n source.onended = () => {\r\n try {\r\n source.disconnect()\r\n gain.disconnect()\r\n } catch {\r\n // ignore\r\n }\r\n this.scheduled = this.scheduled.filter((s) => s.source !== source)\r\n }\r\n this.scheduled.push({ source, gain })\r\n }\r\n void projectEndUs\r\n }\r\n\r\n /** 立即停止所有已排程音频(暂停 / 跳转 / 释放时调用) */\r\n stopAll(): void {\r\n for (const { source, gain } of this.scheduled) {\r\n try {\r\n source.stop()\r\n } catch {\r\n // 未启动或已结束\r\n }\r\n try {\r\n source.disconnect()\r\n gain.disconnect()\r\n } catch {\r\n // ignore\r\n }\r\n }\r\n this.scheduled = []\r\n }\r\n\r\n private applyEnvelope(\r\n gain: GainNode,\r\n startSec: number,\r\n durationSec: number,\r\n clip: TimelineClip,\r\n fadeOffsetUs: number,\r\n ): void {\r\n const base = Math.max(0, clip.volume)\r\n const fadeInSec = clip.fadeInUs / 1_000_000\r\n const fadeOutSec = clip.fadeOutUs / 1_000_000\r\n // 已经越过淡入区时不应再从 0 起(跳转到中段播放)\r\n const passedFadeInUs = fadeOffsetUs\r\n if (fadeInSec > 0 && passedFadeInUs < clip.fadeInUs) {\r\n const remainingIn = (clip.fadeInUs - passedFadeInUs) / 1_000_000\r\n const startRatio = Math.max(0, 1 - passedFadeInUs / clip.fadeInUs)\r\n gain.gain.setValueAtTime(base * startRatio, startSec)\r\n gain.gain.linearRampToValueAtTime(base, startSec + Math.min(remainingIn, durationSec))\r\n } else {\r\n gain.gain.setValueAtTime(base, startSec)\r\n }\r\n if (fadeOutSec > 0) {\r\n const fadeStart = startSec + Math.max(0, durationSec - fadeOutSec)\r\n gain.gain.setValueAtTime(base, fadeStart)\r\n gain.gain.linearRampToValueAtTime(0, startSec + durationSec)\r\n }\r\n }\r\n\r\n private sliceBuffer(\r\n ctx: AudioContext,\r\n entry: PcmEntry,\r\n sourceStartUs: number,\r\n timelineDurationUs: number,\r\n rate: number,\r\n ): AudioBuffer | null {\r\n const sampleRate = entry.sampleRate\r\n const from = Math.floor((sourceStartUs / 1_000_000) * sampleRate)\r\n // 源素材实际消费的长度 = 时间轴时长 × 速率\r\n const sourceFrames = Math.floor((timelineDurationUs / 1_000_000) * sampleRate * rate)\r\n if (sourceFrames <= 0) return null\r\n\r\n const channelCount = Math.max(1, Math.min(2, entry.channels.length))\r\n const buffer = ctx.createBuffer(channelCount, sourceFrames, sampleRate)\r\n for (let ch = 0; ch < channelCount; ch += 1) {\r\n const src = entry.channels[ch] ?? entry.channels[0]\r\n if (!src) continue\r\n const out = buffer.getChannelData(ch)\r\n const limit = Math.min(sourceFrames, src.length - from)\r\n if (limit <= 0) continue\r\n out.set(src.subarray(from, from + limit), 0)\r\n }\r\n return buffer\r\n }\r\n\r\n private async getEntry(assetId: string): Promise<PcmEntry | null> {\r\n const promise = this.pcmCache.get(assetId)\r\n if (!promise) return null\r\n try {\r\n return await promise\r\n } catch {\r\n return null\r\n }\r\n }\r\n\r\n dispose(): void {\r\n this.stopAll()\r\n for (const promise of this.pcmCache.values()) {\r\n void promise.then((e) => e.clip?.destroy()).catch(() => undefined)\r\n }\r\n this.pcmCache.clear()\r\n try {\r\n this.masterGain?.disconnect()\r\n } catch {\r\n // ignore\r\n }\r\n this.masterGain = null\r\n this.ctx = null\r\n }\r\n\r\n /** 调试用:当前排程的音频源数量(验证预览音频是否真的在播放) */\r\n get scheduledCount(): number {\r\n return this.scheduled.length\r\n }\r\n\r\n /** 调试用:已缓存的素材 PCM 数量 */\r\n get cachedAssetCount(): number {\r\n return this.pcmCache.size\r\n }\r\n}\r\n","/**\r\n * 编辑器运行时(Engine Runtime)。\r\n *\r\n * 一个实例 = 一份完整的运行期资源:\r\n * - `ClipRegistry`:IClip 母版与派生实例\r\n * - `PlaybackClock`:基于 AudioContext 的主时钟\r\n * - `PreviewAudioEngine`:预览音频排程\r\n *\r\n * 之所以把它们收拢到一个对象:这三个组件共享同一个 `AudioContext`,生命周期必须\r\n * 严格一致。过去它们是模块级单例,导致同页多实例 / Electron 多窗口无法隔离,\r\n * 且切项目时无法真正释放(AudioContext 数量有上限,Chrome 大约 6 个)。\r\n *\r\n * 注意:本文件**不** import 任何 `@webav/*` 值,只依赖类型,因此可以安全地\r\n * 由 `createEditor()` 静态引入(真正的 WebAV 模块由 `engine/loader` 懒加载)。\r\n */\r\n\r\nimport type { IClip } from '@webav/av-cliper'\r\nimport type { MediaAsset } from '../types'\r\nimport { ClipRegistry } from '../engine/webav/clip-registry'\r\nimport { PlaybackClock } from '../engine/clock/playback-clock'\r\nimport { PreviewAudioEngine } from '../engine/clock/preview-audio'\r\n\r\nexport interface EditorRuntime {\r\n readonly clips: ClipRegistry\r\n readonly clock: PlaybackClock\r\n readonly audio: PreviewAudioEngine\r\n /** 释放全部资源;幂等 */\r\n dispose(): Promise<void>\r\n}\r\n\r\n/**\r\n * 创建运行时。\r\n *\r\n * 不在这里创建 AudioContext:浏览器的自动播放策略要求 AudioContext 必须由用户\r\n * 手势创建或 resume,因此延迟到第一次播放时由 `clock.ensureContext()` 触发。\r\n * 这样初始化(含 SSR 预渲染、Electron 窗口预热)不会产生被挂起的音频上下文。\r\n */\r\nexport function createEditorRuntime(): EditorRuntime {\r\n const clips = new ClipRegistry()\r\n const clock = new PlaybackClock()\r\n const audio = new PreviewAudioEngine()\r\n let disposed = false\r\n\r\n return {\r\n clips,\r\n clock,\r\n audio,\r\n\r\n async dispose() {\r\n if (disposed) return\r\n disposed = true\r\n // 顺序敏感:先停音频与播放(二者都依赖 AudioContext),再销毁 clip,\r\n // 最后关闭 AudioContext —— 反过来会让仍在排程的音频源持有已销毁的 PCM。\r\n audio.stopAll()\r\n clock.pause()\r\n audio.dispose()\r\n clips.dispose()\r\n await clock.dispose()\r\n },\r\n }\r\n}\r\n\r\n/** 素材在运行时中的绑定信息(供 UI 层判断能否播放) */\r\nexport interface AssetRuntimeInfo {\r\n asset: MediaAsset\r\n hasFile: boolean\r\n hasMaster: IClip | null\r\n}\r\n","/**\n * 编辑器实例(Editor Handle)。\n *\n * 这是对外的统一门面:UI 包、接入层、宿主都只跟它打交道。它负责:\n * - 持有 store(工程 + 选择 + 播放头)与运行时(clips / clock / audio)\n * - 暴露命令式 API(play / seek / addFiles / export / undo …)\n * - 通过事件总线对外广播状态变化\n *\n * 设计原则:命令直接调 store / runtime,**不经过 React**。这样:\n * - `initCutWorkbench()` 这种非 React 接入方式可以完整操作编辑器;\n * - 事件总线成为唯一对外通知渠道,接入方不必知道内部 store 结构。\n *\n * 文件导入与导出会**懒加载**引擎的对应模块:宿主调 `addFiles()` / `export()`\n * 时不需要自己写 `await import('@cut-workbench/core/engine')`。\n */\n\nimport type { MediaAsset, Project, TimelineClip, Track } from '../types'\nimport type { SerializedProject } from '../project/schema'\nimport { deserializeProject, serializeProject } from '../project/schema'\nimport { createEditorStore } from './editor-store'\nimport { createHistoryStore } from './history-store'\nimport { createEditorRuntime, type EditorRuntime } from './runtime'\nimport { createEventBus, type EventBus } from './event-bus'\nimport { createEmptyProject, getProjectDuration } from '../timeline/model'\nimport { createMemoryStorage } from '../project/indexeddb-storage'\nimport type { StoragePort, SaveResult } from '../project/storage'\nimport type { HostBridge, SaveFileResult } from '../ports/host-bridge'\nimport { createWebBridge } from '../ports/web-bridge'\nimport { detectEnvironment, type CapabilityReport } from '../capability'\nimport { ExportCancelledError } from '../errors'\nimport {\n createExportFilename,\n resolveExportSettings,\n type ExportHandle,\n type ExportRequest,\n type ExportResult,\n} from '../export/types'\nimport type {\n AddFilesOptions,\n AddFilesResult,\n AddSubtitleAssetParams,\n AddTextAssetParams,\n AutoSaveOptions,\n CutEditorHandle,\n CutEventMap,\n EditorCommands,\n EditorOptions,\n ImportJsonOptions,\n ImportJsonResult,\n SaveAsResult,\n} from './handle-types'\n\nexport type {\n AddFilesOptions,\n AddFilesResult,\n AddSubtitleAssetParams,\n AddTextAssetParams,\n AutoSaveOptions,\n CutEditorHandle,\n CutEventMap,\n EditorCommands,\n EditorOptions,\n ImportJsonOptions,\n ImportJsonResult,\n SaveAsResult,\n}\n\nexport type { EditorRuntime }\n\nfunction resolveBridge(bridge: HostBridge | undefined): HostBridge {\n if (bridge != null) return bridge\n // 只在浏览器里才会真正调用 files 相关方法,Node 环境下构造是安全的\n return createWebBridge()\n}\n\n// 引擎模块按需加载:WebAV 的 opfs-tools 在模块加载期访问 navigator.storage,\n// 静态引入会破坏「Node 里 import 本包」以及非 Chromium 的初始化路径。\nconst loadImportFiles = () => import('../engine/webav/import-files')\nconst loadImportPipeline = () => import('../engine/webav/import-pipeline')\nconst loadExportRunner = () => import('../engine/webav/export-runner')\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n\n/**\n * 创建编辑器实例。\n *\n * 不加载 WebAV:引擎模块在首次导入素材 / 导出时才动态加载 —— opfs-tools\n * 在模块加载期就会访问 `navigator.storage`,非 Chromium 环境会直接抛错。\n */\nexport function createEditor(options: EditorOptions = {}): CutEditorHandle {\n const id = `editor_${Math.random().toString(36).slice(2, 10)}`\n const bridge = resolveBridge(options.bridge)\n\n // —— 持久化目标:可被 saveAs() 替换 ——\n let storage: StoragePort = options.storage ?? createMemoryStorage()\n let namespace = options.namespace ?? 'default'\n let autoSave: AutoSaveOptions | null = options.autoSave ?? null\n\n // projectId 只影响「空工程」;传了完整工程时以工程自身 id 为准\n const initialProject: Project =\n options.project ??\n (options.projectId != null && options.projectId !== ''\n ? { ...createEmptyProject(), id: options.projectId }\n : createEmptyProject())\n\n const bus: EventBus<CutEventMap> = createEventBus((err) => {\n bus.emit('error', { scope: 'event-listener', error: err })\n })\n const history = createHistoryStore()\n const store = createEditorStore({\n project: initialProject,\n assets: options.assets,\n history,\n })\n const runtime = createEditorRuntime()\n\n let disposed = false\n let lastPlaying = false\n let capabilityPromise: Promise<CapabilityReport> | null = null\n let autoSaveTimer: ReturnType<typeof setTimeout> | null = null\n\n // —— 把 store 的变化转成对外事件 ——\n // 只做「引用比较 → 派发」,不做深比较:store 已经是不可变更新,引用变化即语义变化。\n let prevProject = store.getState().project\n let prevAssets = store.getState().assets\n let prevSelection = store.getState().selectedClipIds\n let prevAssetId = store.getState().selectedAssetId\n let prevPlayhead = store.getState().playheadUs\n let prevCanUndo = history.getState().canUndo()\n let prevDirty = store.getState().dirty\n\n /** 当前写盘的存储与命名空间(autoSave 指定了自己的目标时以它为准) */\n function saveTarget(): { port: StoragePort; ns: string } {\n if (autoSave != null) return { port: autoSave.storage, ns: autoSave.namespace }\n return { port: storage, ns: namespace }\n }\n\n function scheduleAutoSave(): void {\n if (autoSave == null || disposed || autoSaveTimer != null) return\n autoSaveTimer = setTimeout(() => {\n autoSaveTimer = null\n // 失败通过 project:saved 广播,宿主自行决定是否提示\n void editor.save().catch((err: unknown) => {\n bus.emit('error', { scope: 'auto-save', error: err })\n })\n }, autoSave.intervalMs ?? 2000)\n }\n\n // 保留退订句柄:dispose 时必须解除,否则 store 会通过闭包一直持有已销毁实例\n const unsubscribeStore = store.subscribe((state) => {\n // dispose 之后 store 仍可被外部写入,但不应再向已销毁的实例广播\n if (disposed) return\n if (state.project !== prevProject) {\n prevProject = state.project\n bus.emit('project:changed', { project: state.project, dirty: state.dirty })\n }\n if (state.dirty !== prevDirty) {\n prevDirty = state.dirty\n bus.emit('project:dirty', { dirty: state.dirty })\n if (state.dirty && autoSave != null) scheduleAutoSave()\n }\n if (state.assets !== prevAssets) {\n prevAssets = state.assets\n bus.emit('assets:changed', { assets: state.assets })\n }\n if (state.selectedClipIds !== prevSelection || state.selectedAssetId !== prevAssetId) {\n prevSelection = state.selectedClipIds\n prevAssetId = state.selectedAssetId\n bus.emit('selection:changed', {\n clipIds: state.selectedClipIds,\n assetId: state.selectedAssetId,\n })\n }\n if (state.playheadUs !== prevPlayhead || state.playing !== lastPlaying) {\n prevPlayhead = state.playheadUs\n lastPlaying = state.playing\n bus.emit('playhead:changed', { us: state.playheadUs, playing: state.playing })\n }\n const canUndo = history.getState().canUndo()\n if (canUndo !== prevCanUndo) {\n prevCanUndo = canUndo\n bus.emit('history:changed', {\n canUndo,\n canRedo: history.getState().canRedo(),\n })\n }\n })\n\n const commands: EditorCommands = {\n updateClip: (clipId, patch, recordHistory, mergeKey) =>\n store.getState().updateClip(clipId, patch, recordHistory, mergeKey),\n moveClip: (clipId, trackId, startUs, recordHistory) =>\n store.getState().moveClip(clipId, trackId, startUs, recordHistory),\n trimClip: (clipId, edge, deltaUs, recordHistory) =>\n store.getState().trimClip(clipId, edge, deltaUs, recordHistory),\n splitClipsAt: (atUs, clipIds) => store.getState().splitClipsAt(atUs, clipIds),\n duplicateSelection: (offsetUs) => store.getState().duplicateSelection(offsetUs),\n addTrack: (kind) => store.getState().addTrack(kind),\n removeTrack: (trackId) => store.getState().removeTrack(trackId),\n updateTrack: (trackId, patch) => store.getState().updateTrack(trackId, patch),\n }\n\n const editor: CutEditorHandle = {\n id,\n store,\n history,\n runtime,\n bridge,\n get storage() {\n return saveTarget().port\n },\n events: bus,\n commands,\n\n // —— 工程 ——\n getProject: () => store.getState().project,\n getProjectId: () => store.getState().project.id,\n\n setProjectId(nextId) {\n if (nextId === store.getState().project.id) return\n store.getState().setProjectId(nextId)\n },\n\n renameProject: (name) => store.getState().renameProject(name),\n setProjectSize: (width, height, mode) => store.getState().setProjectSize(width, height, mode),\n getAssets: () => store.getState().assets,\n getState: () => store.getState(),\n\n setProject(project, assets, missingAssetIds = []) {\n store.getState().setProject(project, assets, missingAssetIds)\n history.getState().clear()\n },\n\n serialize: () => serializeProject(store.getState().project, store.getState().assets),\n exportJson: () => serializeProject(store.getState().project, store.getState().assets),\n\n async load(input) {\n if ('schemaVersion' in input) {\n const { project, assets, missingAssetIds } = deserializeProject(input)\n editor.setProject(project, assets, missingAssetIds)\n bus.emit('project:loaded', {\n project,\n assetCount: assets.length,\n missing: missingAssetIds.length,\n })\n return\n }\n editor.setProject(input, [])\n bus.emit('project:loaded', { project: input, assetCount: 0, missing: 0 })\n },\n\n async importJson(data: SerializedProject, importOptions: ImportJsonOptions = {}) {\n const { project, assets, missingAssetIds } = deserializeProject(data)\n editor.setProject(project, assets, missingAssetIds)\n\n const stillMissing = new Set(missingAssetIds)\n if (importOptions.resolveFile != null) {\n const { importFile } = await loadImportPipeline()\n for (const asset of assets) {\n if (asset.kind === 'text' || asset.kind === 'subtitle') continue\n let file: File | null\n try {\n file = await importOptions.resolveFile(asset)\n } catch (err) {\n bus.emit('error', { scope: 'import-json:resolve-file', error: err })\n continue\n }\n if (file == null) continue\n try {\n // 复用原素材 id:时间轴上的 clip 无需改动即可恢复\n const result = await importFile(runtime.clips, file, undefined, { id: asset.id })\n store.getState().replaceAsset(asset.id, { ...result.asset, id: asset.id })\n stillMissing.delete(asset.id)\n } catch (err) {\n bus.emit('error', { scope: 'import-json:reconnect', error: err })\n }\n }\n }\n\n const remaining = [...stillMissing]\n store.getState().setMissingAssetIds(remaining)\n bus.emit('project:loaded', {\n project: store.getState().project,\n assetCount: assets.length,\n missing: remaining.length,\n })\n return { missingAssetIds: remaining }\n },\n\n async save(): Promise<SaveAsResult> {\n const { port, ns } = saveTarget()\n const result: SaveResult = await port.save(ns, editor.serialize())\n if (result.ok) {\n store.getState().markSaved()\n bus.emit('project:saved', result.path != null ? { ok: true, path: result.path } : { ok: true })\n } else {\n bus.emit('project:saved', {\n ok: false,\n ...(result.error != null ? { error: result.error } : {}),\n ...(result.path != null ? { path: result.path } : {}),\n })\n }\n return result\n },\n\n async saveAs(nextStorage, nextNamespace): Promise<SaveAsResult> {\n storage = nextStorage\n namespace = nextNamespace\n if (autoSave != null) autoSave = { ...autoSave, storage: nextStorage, namespace: nextNamespace }\n return editor.save()\n },\n\n isDirty: () => store.getState().dirty,\n isEmpty: () => store.getState().project.clips.length === 0,\n\n // —— 素材 ——\n async addFiles(files: File[], addOptions: AddFilesOptions = {}): Promise<AddFilesResult> {\n const { importFiles } = await loadImportFiles()\n return importFiles(runtime.clips, store, files, addOptions)\n },\n\n async addTextAsset(params: AddTextAssetParams): Promise<MediaAsset> {\n const { createTextAsset } = await loadImportPipeline()\n const { project } = store.getState()\n const { asset } = createTextAsset({\n registry: runtime.clips,\n text: params.text,\n width: project.width,\n height: project.height,\n ...(params.style != null ? { style: params.style } : {}),\n ...(params.durationUs != null ? { durationUs: params.durationUs } : {}),\n })\n store.getState().addAsset(asset)\n return asset\n },\n\n async addSubtitleAsset(params: AddSubtitleAssetParams): Promise<MediaAsset> {\n const { createSubtitleAsset } = await loadImportPipeline()\n const { parseSrtDurationUs } = await import('../media/srt')\n const { project } = store.getState()\n const durationUs =\n params.durationUs != null && params.durationUs > 0\n ? params.durationUs\n : parseSrtDurationUs(params.srt)\n const { asset } = createSubtitleAsset({\n registry: runtime.clips,\n srt: params.srt,\n width: project.width,\n height: project.height,\n ...(durationUs > 0 ? { durationUs } : {}),\n ...(params.style != null ? { style: params.style } : {}),\n })\n store.getState().addAsset(asset)\n return asset\n },\n\n async reconnectAsset(assetId, file) {\n const { importFile } = await loadImportPipeline()\n if (!store.getState().assets.some((a) => a.id === assetId)) {\n throw new Error(`素材不存在:${assetId}`)\n }\n const result = await importFile(runtime.clips, file, undefined, { id: assetId })\n store.getState().replaceAsset(assetId, { ...result.asset, id: assetId })\n store.getState().clearMissing(assetId)\n },\n\n getMissingAssetIds: () => [...store.getState().missingAssetIds],\n\n removeAsset(assetId) {\n runtime.clips.removeAsset(assetId)\n store.getState().removeAsset(assetId)\n },\n\n // —— 播放 ——\n play() {\n const { project, playing } = store.getState()\n if (playing) return\n const total = getProjectDuration(project)\n const from = total > 0 && store.getState().playheadUs >= total ? 0 : store.getState().playheadUs\n runtime.clock.play(from)\n store.getState().setPlaying(true)\n store.getState().setPlayhead(from)\n },\n\n pause() {\n runtime.clock.pause()\n runtime.audio.stopAll()\n store.getState().setPlaying(false)\n store.getState().setPlayhead(runtime.clock.positionUs)\n },\n\n toggle() {\n if (store.getState().playing) editor.pause()\n else editor.play()\n },\n\n seek(us) {\n const total = getProjectDuration(store.getState().project)\n const clamped = Math.max(0, total > 0 ? Math.min(us, total) : us)\n runtime.clock.seek(clamped)\n store.getState().setPlayhead(clamped)\n },\n\n getPlayhead: () => store.getState().playheadUs,\n getDuration: () => getProjectDuration(store.getState().project),\n isPlaying: () => store.getState().playing,\n\n // —— 编辑 ——\n undo: () => store.getState().undo(),\n redo: () => store.getState().redo(),\n canUndo: () => history.getState().canUndo(),\n canRedo: () => history.getState().canRedo(),\n addClip: (clip: TimelineClip) => store.getState().addClip(clip),\n removeClips: (ids) => store.getState().removeClips(ids),\n selectClips: (ids) => store.getState().selectClips(ids),\n getSelectedClipIds: () => [...store.getState().selectedClipIds],\n setSelection(clipIds, assetId) {\n store.getState().selectClips(clipIds)\n if (assetId !== undefined) store.getState().selectAsset(assetId)\n },\n updateClip: (clipId, patch, recordHistory, mergeKey) =>\n store.getState().updateClip(clipId, patch, recordHistory, mergeKey),\n moveClip: (clipId, trackId, startUs, recordHistory) =>\n store.getState().moveClip(clipId, trackId, startUs, recordHistory),\n trimClip: (clipId, edge, deltaUs, recordHistory) =>\n store.getState().trimClip(clipId, edge, deltaUs, recordHistory),\n splitClipsAt: (atUs, clipIds) => store.getState().splitClipsAt(atUs, clipIds),\n duplicateSelection: (offsetUs) => store.getState().duplicateSelection(offsetUs),\n addTrack: (kind: Track['kind']) => store.getState().addTrack(kind),\n removeTrack: (trackId) => store.getState().removeTrack(trackId),\n updateTrack: (trackId, patch) => store.getState().updateTrack(trackId, patch),\n\n // —— 导出 ——\n export(request: ExportRequest = {}): ExportHandle {\n const snapshot = store.getState()\n const settings = resolveExportSettings(snapshot.project, request.settings)\n const mimeType = request.mimeType ?? 'video/mp4'\n const filename = request.filename ?? createExportFilename()\n\n bus.emit('export:start', {\n totalUs: getProjectDuration(snapshot.project),\n settings,\n })\n\n let cancelled = false\n let inner: { cancel: () => void } | null = null\n\n const cancel = () => {\n cancelled = true\n inner?.cancel()\n }\n\n const promise = (async (): Promise<ExportResult> => {\n try {\n const { startExport, computeExportDuration } = await loadExportRunner()\n if (cancelled) throw new ExportCancelledError()\n\n const handle = startExport(\n {\n registry: runtime.clips,\n project: snapshot.project,\n assets: snapshot.assets,\n settings,\n maxTimeUs: computeExportDuration(snapshot.project),\n mimeType,\n filename,\n ...(bridge.createExportWorker != null\n ? {\n createWorker: () => {\n try {\n return bridge.createExportWorker?.() ?? null\n } catch (err) {\n bus.emit('error', { scope: 'export:worker', error: err })\n return null\n }\n },\n }\n : {}),\n },\n (progress) => {\n if (!cancelled) bus.emit('export:progress', { progress })\n },\n )\n inner = handle\n\n const result = await handle.promise\n if (cancelled) throw new ExportCancelledError()\n // 顶层 blob / filename 是给旧订阅方的兼容副本,新代码请用 result\n bus.emit('export:done', { result, blob: result.blob, filename: result.filename })\n return result\n } catch (err) {\n if (cancelled || err instanceof ExportCancelledError) {\n // 用户取消不是错误:不广播 export:error\n throw err instanceof ExportCancelledError ? err : new ExportCancelledError()\n }\n bus.emit('export:error', { message: errorMessage(err), error: err })\n throw err\n }\n })()\n\n return { promise, cancel }\n },\n\n async saveExport(result: ExportResult, suggestedName?: string): Promise<SaveFileResult> {\n const saveBlob = bridge.files?.saveBlob\n if (saveBlob == null) {\n return { ok: false, error: '宿主未提供 saveBlob,无法保存成片' }\n }\n try {\n return await saveBlob.call(bridge.files, result.blob, suggestedName ?? result.filename)\n } catch (err) {\n return { ok: false, error: errorMessage(err) }\n }\n },\n\n // —— 环境 ——\n getCapabilities(): Promise<CapabilityReport> {\n capabilityPromise ??= detectEnvironment()\n return capabilityPromise\n },\n\n // —— 生命周期 ——\n async dispose() {\n if (disposed) return\n disposed = true\n if (autoSaveTimer != null) {\n clearTimeout(autoSaveTimer)\n autoSaveTimer = null\n }\n unsubscribeStore()\n bus.emit('dispose', {})\n await runtime.dispose()\n bus.clear()\n },\n }\n\n return editor\n}\n","/**\n * 批量导入素材(core 的唯一实现)。\n *\n * 为什么放在 core:导入语义(重连缺失素材、id 注入、失败汇总、串行解码)\n * 被三处调用 —— `<CutWorkbench>` 的 `useMediaImport`、`initCutWorkbench().addFiles`、\n * 以及 `editor.addFiles`。过去 workbench 里另有一份实现,行为与 init 那份不一致\n * (init 不重连、不报进度),宿主换个入口就得到不同结果。\n *\n * 本文件位于 `engine/webav` 下,因此允许 import `@webav/*`(经 importFile 间接)。\n * 编辑器句柄会**动态**加载它,宿主不需要自己写动态 import。\n */\n\nimport type { StoreApi } from 'zustand/vanilla'\nimport type { MediaAsset } from '../../types'\nimport type { EditorState } from '../../editor/editor-store'\nimport { detectFileType, UnsupportedFileError } from '../../media/file-reader'\nimport type { ClipRegistry } from './clip-registry'\nimport { importFile } from './import-pipeline'\n\nexport interface ImportFilesOptions {\n /** 导入成功后选中第一个素材;默认 true */\n select?: boolean\n /** 逐文件进度:index 为入参数组下标,progress 为 0~1 */\n onProgress?: (index: number, progress: number) => void\n /** 由宿主指定素材 id;返回 falsy 则回退到库生成的 `asset_xxx` */\n idFor?: (file: File, index: number) => string | null | undefined\n /** 与缺失源文件的素材同名同类型时重连而非新增;默认 true */\n reconnect?: boolean\n}\n\nexport interface ImportFilesResult {\n imported: MediaAsset[]\n failures: Array<{ name: string; error: string }>\n /** 其中走重连的数量 */\n reconnected: number\n}\n\n/** 找出可以「重连」的素材 id(同名 + 同类型 + 当前确实缺少二进制) */\nfunction findReconnectableId(\n state: EditorState,\n registry: ClipRegistry,\n file: File,\n kind: MediaAsset['kind'],\n): string | null {\n if (state.missingAssetIds.length === 0) return null\n const candidate = state.assets.find(\n (asset) =>\n state.missingAssetIds.includes(asset.id) &&\n asset.kind === kind &&\n asset.name === file.name &&\n registry.getFile(asset.id) == null,\n )\n return candidate?.id ?? null\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n\n/**\n * 批量导入素材。\n *\n * - 串行处理,避免同时开辟多个解码器把内存打爆;\n * - 单个文件失败只影响自身,最后汇总返回;\n * - 进度由调用方通过 `onProgress` 展示(不写 store,避免高频全局重渲染);\n * - 与「缺失源文件」的素材同名同类型时重连,复用原 assetId。\n */\nexport async function importFiles(\n registry: ClipRegistry,\n store: StoreApi<EditorState>,\n files: File[],\n options: ImportFilesOptions = {},\n): Promise<ImportFilesResult> {\n const { select = true, onProgress, idFor, reconnect = true } = options\n const imported: MediaAsset[] = []\n const failures: ImportFilesResult['failures'] = []\n let reconnected = 0\n\n for (let index = 0; index < files.length; index += 1) {\n const file = files[index]\n if (file == null) continue\n try {\n const detected = detectFileType(file)\n const reconnectId = reconnect\n ? findReconnectableId(store.getState(), registry, file, detected.kind)\n : null\n\n // 宿主指定 id 优先;重连时固定复用原 id(时间轴上的 clip 靠它恢复)\n const requestedId = reconnectId ?? idFor?.(file, index) ?? null\n const assetId = requestedId != null && requestedId !== '' ? requestedId : undefined\n\n if (reconnectId != null) {\n // 先释放旧母版与缩略图缓存,让新文件重新抽帧(同 id,缓存会命中旧帧)\n registry.removeAsset(reconnectId)\n }\n\n const result = await importFile(\n registry,\n file,\n (progress) => onProgress?.(index, progress),\n assetId != null ? { id: assetId } : undefined,\n )\n\n if (reconnectId != null) {\n store.getState().replaceAsset(reconnectId, result.asset)\n store.getState().clearMissing(reconnectId)\n imported.push(result.asset)\n reconnected += 1\n } else {\n store.getState().addAsset(result.asset)\n imported.push(result.asset)\n }\n } catch (err) {\n const message = errorMessage(err)\n failures.push({ name: file.name, error: message })\n // 失败时也把进度推满,调用方不用自己判断「这个文件已经结束了」\n onProgress?.(index, 1)\n if (!(err instanceof UnsupportedFileError)) {\n // 非「类型不支持」的异常通常是解码器问题,值得留一条痕迹\n console.warn('[importFiles] 导入失败', file.name, err)\n }\n }\n }\n\n if (select && imported.length > 0) {\n store.getState().selectAsset(imported[0]?.id ?? null)\n }\n\n return { imported, failures, reconnected }\n}\n","/**\n * 时间轴数据 → Sprite 的转换。\n *\n * 关键点(实测自 1.2.8):\n * - `sprite.rect` 坐标系以 sprite 中心为原点,`rect.x/y` 是**左上角**坐标,\n * 但 `BaseSprite._render` 内部用 `center` 做 translate,绘制时以中心为基准;\n * - `sprite.time.duration` 必须有限,否则 Combinator 无法推断结束时间;\n * - `playbackRate !== 1` 时音频是线性插值变速(会变调)。\n */\n\nimport { OffscreenSprite, VisibleSprite } from '@webav/av-cliper'\nimport type { IClip } from '@webav/av-cliper'\nimport type { TimelineClip } from '../../types'\nimport { asUs } from '../../timeline/time'\n\nexport type SpriteKind = 'visible' | 'offscreen'\n\nexport interface SpriteBuildContext {\n /** 该 sprite 的 zIndex(轨道顺序 + 轨道内顺序) */\n zIndex: number\n}\n\n/**\n * 将时间轴 clip 的属性写入 sprite。\n *\n * @param sprite 目标 sprite(VisibleSprite / OffscreenSprite 均可)\n * @param clip 时间轴 clip(时间单位微秒,transform.angle 为角度)\n */\nexport function applyClipToSprite(\n sprite: VisibleSprite | OffscreenSprite,\n clip: TimelineClip,\n): void {\n sprite.rect.x = clip.transform.x\n sprite.rect.y = clip.transform.y\n sprite.rect.w = clip.transform.w\n sprite.rect.h = clip.transform.h\n sprite.rect.angle = (clip.transform.angle * Math.PI) / 180\n sprite.opacity = clip.opacity\n sprite.flip = clip.flip\n sprite.time = {\n offset: asUs(clip.startUs),\n duration: asUs(clip.durationUs),\n playbackRate: clip.playbackRate,\n }\n}\n\nexport function createVisibleSprite(\n instance: IClip,\n clip: TimelineClip,\n ctx: SpriteBuildContext,\n): VisibleSprite {\n const sprite = new VisibleSprite(instance)\n applyClipToSprite(sprite, clip)\n sprite.zIndex = ctx.zIndex\n sprite.visible = true\n /*\n * 关闭 WebAV 内建的选中框与拖拽,由预览层用 DOM 自绘选框。\n *\n * `disabled` 只影响「能否被 AVCanvas 选中/拖动」,不影响 `rect.checkHit`\n * (WebAV 的命中测试不看 interactable),所以画布内点选仍然可用,\n * 只是改由我们自己的 hitTest + 自绘选框负责,样式与性能都可控。\n */\n sprite.interactable = 'disabled'\n return sprite\n}\n\nexport function createOffscreenSprite(\n instance: IClip,\n clip: TimelineClip,\n ctx: SpriteBuildContext,\n): OffscreenSprite {\n const sprite = new OffscreenSprite(instance)\n applyClipToSprite(sprite, clip)\n sprite.zIndex = ctx.zIndex\n return sprite\n}\n\n/**\n * 透明度淡入淡出(预览可见,导出时同样生效)。\n * 仅在 fadeIn/fadeOut 大于 0 时挂动画,避免无谓的逐帧 setter 调用。\n */\nexport function applyOpacityFade(\n sprite: VisibleSprite | OffscreenSprite,\n clip: TimelineClip,\n): void {\n if (clip.fadeInUs <= 0 && clip.fadeOutUs <= 0) return\n const duration = Math.max(1, clip.durationUs)\n const fadeInRatio = clip.fadeInUs > 0 ? Math.min(0.5, clip.fadeInUs / duration) : 0\n const fadeOutRatio = clip.fadeOutUs > 0 ? Math.min(0.5, clip.fadeOutUs / duration) : 0\n const keyFrames: Record<string, { opacity: number }> = {}\n if (fadeInRatio > 0) {\n keyFrames['0%'] = { opacity: 0 }\n keyFrames[`${Math.round(fadeInRatio * 100)}%`] = { opacity: clip.opacity }\n }\n if (fadeOutRatio > 0) {\n keyFrames[`${Math.round((1 - fadeOutRatio) * 100)}%`] = { opacity: clip.opacity }\n keyFrames['100%'] = { opacity: 0 }\n }\n if (Object.keys(keyFrames).length < 2) return\n sprite.setAnimation(keyFrames, { duration, iterCount: 1 })\n}\n","/**\n * 自定义 IClip:给内部 clip 套一层音量包络(音量 + 淡入淡出)。\n *\n * 为什么需要包装而不是直接改 `MP4Clip.opts.volume`:\n * - `MP4Clip` / `AudioClip` 的音量只能在构造时给定,且 `clone()` 会沿用原 opts;\n * - 同一个素材可能被多个时间轴 clip 复用,每个 clip 的音量/淡入淡出都不同;\n * - `Combinator.addSprite()` 内部会 clone sprite,因此必须在「每次 addSprite 用到的实例」\n * 上做包装,才能让每个 clip 独立生效。\n *\n * 单位:时间参数为微秒。\n */\n\nimport type { IClip } from '@webav/av-cliper'\nimport type { ClipMetaBase } from './meta'\n\nexport interface GainClipOptions {\n /** 线性音量 0~1 */\n volume: number\n /** 淡入时长(微秒,相对该 clip 起点) */\n fadeInUs: number\n /** 淡出时长(微秒,相对该 clip 终点) */\n fadeOutUs: number\n /** clip 总时长(微秒) */\n durationUs: number\n /** 播放速率:tick 内部时间 = 时间轴时间 × rate */\n playbackRate: number\n}\n\nexport class GainClip implements IClip {\n readonly ready: Promise<ClipMetaBase>\n private inner: IClip\n private opts: GainClipOptions\n private destroyed = false\n\n constructor(inner: IClip, opts: GainClipOptions) {\n this.inner = inner\n this.opts = opts\n this.ready = inner.ready\n }\n\n get meta(): ClipMetaBase {\n return this.inner.meta\n }\n\n async tick(time: number): Promise<{\n video?: VideoFrame | ImageBitmap | null\n audio?: Float32Array[]\n state: 'done' | 'success'\n }> {\n if (this.destroyed) return { state: 'done' }\n const result = await this.inner.tick(time)\n if (result.audio == null || result.audio.length === 0) return result\n\n const gain = this.gainAt(time)\n if (gain === 1) return result\n return {\n ...result,\n audio: result.audio.map((channel) => {\n const out = new Float32Array(channel.length)\n for (let i = 0; i < channel.length; i += 1) out[i] = (channel[i] ?? 0) * gain\n return out\n }),\n }\n }\n\n /** 计算某个时刻(源时间,微秒)的线性增益 */\n private gainAt(time: number): number {\n const { volume, fadeInUs, fadeOutUs, durationUs, playbackRate } = this.opts\n const base = Math.max(0, Math.min(1, volume))\n const totalUs = Math.max(1, durationUs * (playbackRate > 0 ? playbackRate : 1))\n const fadeIn = Math.min(fadeInUs * (playbackRate > 0 ? playbackRate : 1), totalUs / 2)\n const fadeOut = Math.min(fadeOutUs * (playbackRate > 0 ? playbackRate : 1), totalUs / 2)\n let gain = base\n if (fadeIn > 0 && time < fadeIn) gain *= Math.max(0, time / fadeIn)\n if (fadeOut > 0 && time > totalUs - fadeOut) {\n gain *= Math.max(0, (totalUs - time) / fadeOut)\n }\n return Math.max(0, Math.min(1, gain))\n }\n\n async clone(): Promise<this> {\n const copy = new GainClip(await this.inner.clone(), { ...this.opts })\n await copy.ready\n return copy as this\n }\n\n async split(time: number): Promise<[this, this]> {\n if (time <= 0 || time >= this.opts.durationUs) throw new Error('\"time\" out of bounds')\n const leftInner = await this.inner.clone()\n const rightInner = await this.inner.clone()\n const left = new GainClip(leftInner, { ...this.opts, durationUs: time })\n const right = new GainClip(rightInner, {\n ...this.opts,\n durationUs: this.opts.durationUs - time,\n })\n await Promise.all([left.ready, right.ready])\n return [left as this, right as this]\n }\n\n destroy(): void {\n if (this.destroyed) return\n this.destroyed = true\n this.inner.destroy()\n }\n}\n","/**\n * 导出时的坐标系换算。\n *\n * 时间轴里所有 `clip.transform`(x/y/w/h)都以**工程分辨率**为坐标系\n * (`project.width × project.height`),而导出可以选择 1080p / 720p / 原尺寸。\n * 若直接把工程坐标丢给 Combinator,导出到不同分辨率时素材会停留在左上角\n * 且尺寸不对 —— 这是一个很容易忽略、但一眼就能看出来的 bug。\n *\n * 单位:位置与尺寸是像素,时间为微秒(与本模块无关)。\n */\n\nimport type { ClipTransform, TimelineClip } from '../../types'\n\nexport interface Size {\n width: number\n height: number\n}\n\n/**\n * 把工程坐标下的 transform 等比映射到输出尺寸。\n *\n * 使用同一个缩放系数(分别算宽高再取较小值会改变构图,横竖比不同时更明显),\n * 并按输出画布居中,保证「预览里居中」在导出后依然居中。\n *\n * @param transform 工程坐标系下的变换\n * @param source 工程分辨率\n * @param target 输出分辨率\n */\nexport function mapTransformToOutput(\n transform: ClipTransform,\n source: Size,\n target: Size,\n): ClipTransform {\n const safeSourceW = Math.max(1, source.width)\n const safeSourceH = Math.max(1, source.height)\n const scaleX = target.width / safeSourceW\n const scaleY = target.height / safeSourceH\n // 取较小系数:等比缩放,避免非等比拉伸\n const scale = Math.min(scaleX, scaleY)\n\n const scaledW = transform.w * scale\n const scaledH = transform.h * scale\n\n // 先按比例缩放中心点,再把整体挪到输出画布中心\n const centerX = (transform.x + transform.w / 2) * scaleX\n const centerY = (transform.y + transform.h / 2) * scaleY\n const offsetX = (target.width - safeSourceW * scale) / 2\n const offsetY = (target.height - safeSourceH * scale) / 2\n\n return {\n x: centerX + offsetX - scaledW / 2,\n y: centerY + offsetY - scaledH / 2,\n w: scaledW,\n h: scaledH,\n angle: transform.angle,\n }\n}\n\n/** 便捷版:直接返回换算后的 clip(浅拷贝,只替换 transform) */\nexport function mapClipToOutput(\n clip: TimelineClip,\n source: Size,\n target: Size,\n): TimelineClip {\n return { ...clip, transform: mapTransformToOutput(clip.transform, source, target) }\n}\n","/**\n * 导出流水线:时间轴数据 → Combinator → MP4 二进制。\n *\n * 该模块不依赖 React;`buildCombinator` 同时被主线程与 Worker 复用。\n *\n * ⚠️ `Combinator.output()` 在当前线程同步步进渲染,会阻塞 UI。\n * 主线程路径(`exportProject`)只作为 Worker 不可用时的降级方案,必须配全屏遮罩。\n */\n\nimport { Combinator, fixFMP4Duration } from '@webav/av-cliper'\nimport type { IClip } from '@webav/av-cliper'\nimport type { MediaAsset, TimelineClip, Track } from '../../types'\nimport { clampStillDuration } from '../../timeline/model'\nimport { applyOpacityFade, createOffscreenSprite } from './sprite-factory'\nimport { GainClip } from './gain-clip'\nimport { mapClipToOutput } from './coordinate'\n\nexport interface ExportClipInput {\n clip: TimelineClip\n instance: IClip\n kind: MediaAsset['kind']\n track: Track\n}\n\nexport interface BuildCombinatorOptions {\n width: number\n height: number\n fps: number\n bitrate: number\n includeAudio: boolean\n bgColor?: string\n /**\n * 工程分辨率。\n *\n * 时间轴里的 clip.transform 以工程坐标为基准,导出分辨率可能不同\n * (1080p / 720p / 原尺寸),因此必须按此缩放,否则素材会挤在左上角。\n */\n projectWidth?: number\n projectHeight?: number\n}\n\nexport interface BuiltCombinator {\n combinator: Combinator\n /** 实际输出时长上限(微秒) */\n maxTimeUs: number\n skipped: Array<{ clipId: string; reason: string }>\n}\n\n/**\n * 给 clip 套上音量包络。\n *\n * WebAV 的音频混音是直接相加,没有每轨音量;`MP4Clip` / `AudioClip` 的 volume\n * 只能在构造时指定,且 clone 会沿用(同一素材被多个 clip 复用时无法区分)。\n * 因此统一用 `GainClip` 包装:每个时间轴 clip 走自己的实例与包络。\n *\n * @param instance 该 clip 独占的 IClip\n * @param clip 时间轴数据(提供 volume / fadeIn / fadeOut)\n */\nexport function withClipGain(instance: IClip, clip: TimelineClip): IClip {\n const needsGain =\n clip.muted ||\n clip.volume !== 1 ||\n clip.fadeInUs > 0 ||\n clip.fadeOutUs > 0\n if (!needsGain) return instance\n if (clip.muted) return instance\n return new GainClip(instance, {\n volume: clip.volume,\n fadeInUs: clip.fadeInUs,\n fadeOutUs: clip.fadeOutUs,\n durationUs: clip.durationUs,\n playbackRate: clip.playbackRate,\n })\n}\n\n/**\n * 组装 Combinator。\n *\n * - 只加入未隐藏轨道的 clip;`includeAudio === false` 时跳过音频轨;\n * - 显式给 `output({ maxTime })`,因此不依赖 main sprite(静态素材不会导致\n * \"Unable to determine the end time\");\n * - 音频 sprite 不参与画面,但仍需加入才能混合音轨。\n */\nexport async function buildCombinator(\n inputs: ExportClipInput[],\n opts: BuildCombinatorOptions,\n): Promise<BuiltCombinator> {\n const combinator = new Combinator({\n width: opts.width,\n height: opts.height,\n fps: opts.fps,\n bitrate: opts.bitrate,\n bgColor: opts.bgColor ?? '#000000',\n audio: opts.includeAudio ? undefined : false,\n })\n\n const skipped: Array<{ clipId: string; reason: string }> = []\n const usable = inputs.filter((item) => !item.track.hidden)\n if (usable.length === 0) {\n throw new Error('时间轴为空,无法导出')\n }\n\n // 视觉轨在前(zIndex 小,先绘制),音频轨在后;同轨按起点排序\n const ordered = [...usable].sort((a, b) => {\n const aAudio = a.track.kind === 'audio' ? 1 : 0\n const bAudio = b.track.kind === 'audio' ? 1 : 0\n if (aAudio !== bAudio) return aAudio - bAudio\n if (a.clip.startUs !== b.clip.startUs) return a.clip.startUs - b.clip.startUs\n return a.clip.id.localeCompare(b.clip.id)\n })\n\n // 工程坐标 → 输出坐标\n const sourceSize = {\n width: opts.projectWidth ?? opts.width,\n height: opts.projectHeight ?? opts.height,\n }\n const targetSize = { width: opts.width, height: opts.height }\n\n let maxTimeUs = 0\n let zIndex = 0\n\n for (const item of ordered) {\n const { instance, track } = item\n const clip = mapClipToOutput(item.clip, sourceSize, targetSize)\n const isAudioTrack = track.kind === 'audio'\n if (isAudioTrack && !opts.includeAudio) continue\n if (track.muted) continue\n if (clip.muted) continue\n\n const duration = clampStillDuration(clip.durationUs)\n if (clip.durationUs <= 0) {\n skipped.push({ clipId: clip.id, reason: '时长为 0,已跳过' })\n continue\n }\n\n const normalized: TimelineClip = {\n ...clip,\n durationUs: duration as TimelineClip['durationUs'],\n }\n const sprite = createOffscreenSprite(withClipGain(instance, normalized), normalized, {\n zIndex,\n })\n applyOpacityFade(sprite, normalized)\n\n await combinator.addSprite(sprite, { main: false })\n maxTimeUs = Math.max(maxTimeUs, clip.startUs + duration)\n zIndex += 1\n }\n\n if (!Number.isFinite(maxTimeUs) || maxTimeUs <= 0) {\n throw new Error('无法确定导出时长:请确认时间轴上有有效片段')\n }\n\n return { combinator, maxTimeUs, skipped }\n}\n\nexport interface ExportOptions extends BuildCombinatorOptions {\n /** 输出后修复 fMP4 时长(部分播放器读不到 moov 时长) */\n fixDuration?: boolean\n}\n\n/**\n * 主线程导出(Worker 不可用时的降级路径)。\n *\n * @param inputs 已构建好的 clip 实例(调用方负责 destroy)\n * @param opts 输出配置\n * @param onProgress 0~1 进度回调\n */\nexport async function exportProject(\n inputs: ExportClipInput[],\n opts: ExportOptions,\n onProgress?: (progress: number) => void,\n): Promise<{ buffer: ArrayBuffer; elapsedMs: number; skipped: BuiltCombinator['skipped'] }> {\n const startedAt = performance.now()\n const { combinator, maxTimeUs, skipped } = await buildCombinator(inputs, opts)\n\n const unsubscribe = onProgress\n ? combinator.on('OutputProgress', (p) => onProgress(p))\n : () => undefined\n\n try {\n const stream = combinator.output({ maxTime: maxTimeUs })\n const raw = await new Response(stream).arrayBuffer()\n if (opts.fixDuration) {\n const fixed = await fixFMP4Duration(\n new Response(raw).body as ReadableStream<Uint8Array>,\n )\n return {\n buffer: await new Response(fixed).arrayBuffer(),\n elapsedMs: performance.now() - startedAt,\n skipped,\n }\n }\n return { buffer: raw, elapsedMs: performance.now() - startedAt, skipped }\n } finally {\n unsubscribe()\n combinator.destroy()\n }\n}\n\n/** 检查分辨率 / 帧率 / 码率组合是否可编码 */\nexport async function checkExportSupport(opts: {\n width: number\n height: number\n bitrate: number\n}): Promise<boolean> {\n return Combinator.isSupported({\n width: opts.width,\n height: opts.height,\n bitrate: opts.bitrate,\n })\n}\n\n/** 时间轴结束时间(微秒) */\nexport function computeTimelineEnd(clips: TimelineClip[]): number {\n return clips.reduce((max, c) => Math.max(max, c.startUs + c.durationUs), 0)\n}\n","/**\n * 导出编排(core 版本)。\n *\n * 分层:本模块允许 import `@webav/*`(位于 engine/webav 下),但**不**依赖 React,\n * 也不做任何 UI。上层(`editor.export()` 与 `<CutWorkbench>` 的导出对话框)\n * 都消费同一套逻辑,避免「UI 里能导出、命令式 API 不能导出」的分裂。\n *\n * 两条路径:\n * - 默认走 Worker(不阻塞 UI);\n * - 含文字片段(依赖 DOM)或 Worker 不可用时回退主线程;\n * - 含音频 / 视频音轨时,主线程会先解码 PCM 再传给 Worker\n * (Worker 内没有 AudioContext,AudioClip 无法自行解码)。\n */\n\nimport type { MediaAsset, Project } from '../../types'\nimport type { ExportSettings } from '../../types'\nimport { ExportCancelledError } from '../../errors'\nimport type { ExportResult } from '../../export/types'\nimport { createExportFilename } from '../../export/types'\nimport type { ClipRegistry } from './clip-registry'\nimport {\n checkExportSupport,\n exportProject,\n type ExportClipInput,\n} from './exporter'\nimport { loadPcmForAsset } from './import-pipeline'\nimport type { WorkerAssetPayload, WorkerResponse, WorkerTimelinePayload } from './types'\n\n/** 标记取消的哨兵:只在本模块内部流转,不暴露给宿主 */\nconst CANCELLED = '__export_cancelled__'\n\nexport interface ExportPlanOptions {\n registry: ClipRegistry\n /**\n * Worker 工厂;返回 null 表示不可用,直接走主线程。\n * 不传时使用 `setDefaultExportWorkerFactory` 注册的默认实现(若有)。\n */\n createWorker?: () => Worker | null\n project: Project\n assets: MediaAsset[]\n settings: ExportSettings\n /** 导出上限(微秒),默认全片 */\n maxTimeUs?: number\n /** 输出 Blob 的 MIME,默认 video/mp4 */\n mimeType?: string\n filename?: string\n}\n\nexport interface CoreExportHandle {\n promise: Promise<ExportResult>\n cancel: () => void\n}\n\n/**\n * 默认 Worker 工厂(构建形态相关)。\n *\n * core 不能知道 UI 包把导出 Worker 打成了哪种资源(独立 chunk / UMD 内联),\n * 因此由 `@cut-workbench/workbench` 在模块加载时注册一份;宿主若提供了\n * `HostBridge.createExportWorker`,那个优先级更高。两者都没有时走主线程。\n */\nlet defaultWorkerFactory: (() => Worker | null) | null = null\n\nexport function setDefaultExportWorkerFactory(factory: (() => Worker | null) | null): void {\n defaultWorkerFactory = factory\n}\n\n/** 判断是否可以用 Worker 导出(文字素材依赖 DOM) */\nexport function canUseWorker(project: Project, assets: MediaAsset[]): boolean {\n const kinds = new Map(assets.map((a) => [a.id, a.kind]))\n return !project.clips.some((clip) => kinds.get(clip.assetId) === 'text')\n}\n\n/** 参与导出的片段最大结束时间(跳过隐藏轨道) */\nexport function computeExportDuration(project: Project, maxTimeUs?: number): number {\n if (maxTimeUs != null) return maxTimeUs\n const hidden = new Set(project.tracks.filter((t) => t.hidden).map((t) => t.id))\n return project.clips.reduce(\n (max, clip) => (hidden.has(clip.trackId) ? max : Math.max(max, clip.startUs + clip.durationUs)),\n 0,\n )\n}\n\n/**\n * 构建 Worker 载荷。\n *\n * 音频素材需要主线程预解码 PCM(Worker 无 AudioContext)。为避免无谓解码,\n * 只有「音频轨素材」或「含音轨且会被播放的视频」才解码。\n */\nexport async function buildWorkerPayload(\n registry: ClipRegistry,\n project: Project,\n assets: MediaAsset[],\n settings: ExportSettings,\n maxTimeUs?: number,\n): Promise<{ assets: WorkerAssetPayload[]; timeline: WorkerTimelinePayload; missing: string[] }> {\n const usedAssetIds = new Set(project.clips.map((c) => c.assetId))\n\n const missing: string[] = []\n const payloadAssets: WorkerAssetPayload[] = []\n\n for (const asset of assets) {\n if (!usedAssetIds.has(asset.id)) continue\n const file = registry.getFile(asset.id)\n if (file == null && asset.kind !== 'text' && asset.kind !== 'subtitle') {\n missing.push(asset.name)\n continue\n }\n\n let pcm: Float32Array[] | undefined\n const needsAudio =\n settings.includeAudio &&\n (asset.kind === 'audio' || (asset.kind === 'video' && asset.hasAudio))\n if (needsAudio) {\n pcm = await loadPcmForAsset(registry, asset.id)\n }\n\n payloadAssets.push({\n id: asset.id,\n kind: asset.kind,\n file,\n width: asset.width,\n height: asset.height,\n durationUs: asset.durationUs,\n hasAudio: asset.hasAudio,\n pcm,\n text: asset.text,\n textStyle: asset.textStyle,\n srt: asset.srt,\n subtitleStyle: asset.subtitleStyle,\n })\n }\n\n const kindOf = new Map(assets.map((a) => [a.id, a.kind]))\n const clips = project.clips.map((clip) => ({\n clip,\n kind: kindOf.get(clip.assetId) ?? 'video',\n }))\n\n return {\n assets: payloadAssets,\n timeline: {\n width: settings.width,\n height: settings.height,\n projectWidth: project.width,\n projectHeight: project.height,\n fps: settings.fps,\n bitrate: settings.bitrate,\n includeAudio: settings.includeAudio,\n bgColor: '#000000',\n maxTimeUs,\n clips,\n },\n missing,\n }\n}\n\n/**\n * 主线程回退导出:为每个 clip 从 registry 建实例后交给 core exporter。\n *\n * 注意:实例用 `export:<clipId>` 作为临时 key 注册进 registry,导出结束后统一销毁,\n * 避免污染预览用的实例(两者不可共用,因为共享 tick 状态)。\n */\nasync function exportOnMainThread(\n options: ExportPlanOptions,\n onProgress: (progress: number) => void,\n shouldCancel: () => boolean,\n): Promise<ExportResult> {\n const { registry, project, settings, mimeType = 'video/mp4', filename } = options\n const trackMap = new Map(project.tracks.map((t) => [t.id, t]))\n const inputs: ExportClipInput[] = []\n const skipped: Array<{ clipId: string; reason: string }> = []\n const tempIds: string[] = []\n\n try {\n for (const clip of project.clips) {\n if (shouldCancel()) throw new ExportCancelledError()\n const track = trackMap.get(clip.trackId)\n if (!track || track.hidden) continue\n const asset = registry.getAsset(clip.assetId)\n if (!asset) {\n skipped.push({ clipId: clip.id, reason: '素材缺失' })\n continue\n }\n const tempId = `export:${clip.id}`\n try {\n const instance = await registry.createInstance({\n timelineClipId: tempId,\n assetId: clip.assetId,\n inPointUs: clip.inPointUs,\n durationUs: clip.durationUs * clip.playbackRate,\n text: clip.text,\n textStyle: clip.textStyle,\n srt: clip.srt,\n subtitleStyle: clip.subtitleStyle,\n projectWidth: project.width,\n projectHeight: project.height,\n })\n tempIds.push(tempId)\n inputs.push({ clip, instance, kind: asset.kind, track })\n } catch (err) {\n skipped.push({\n clipId: clip.id,\n reason: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n if (inputs.length === 0) throw new Error('时间轴为空,无法导出')\n if (shouldCancel()) throw new ExportCancelledError()\n\n const startedAt = performance.now()\n const { buffer, skipped: buildSkipped } = await exportProject(\n inputs,\n {\n width: settings.width,\n height: settings.height,\n fps: settings.fps,\n bitrate: settings.bitrate,\n includeAudio: settings.includeAudio,\n projectWidth: project.width,\n projectHeight: project.height,\n },\n onProgress,\n )\n if (shouldCancel()) throw new ExportCancelledError()\n\n return {\n blob: new Blob([buffer], { type: mimeType }),\n filename: filename ?? createExportFilename(),\n mimeType,\n durationUs: computeExportDuration(project, options.maxTimeUs),\n width: settings.width,\n height: settings.height,\n fps: settings.fps,\n bitrate: settings.bitrate,\n elapsedMs: performance.now() - startedAt,\n skipped: [...skipped, ...buildSkipped],\n mode: 'main',\n }\n } finally {\n for (const id of tempIds) registry.destroyInstance(id)\n }\n}\n\nasync function exportViaWorker(\n options: ExportPlanOptions,\n worker: Worker,\n onProgress: (progress: number) => void,\n shouldCancel: () => boolean,\n): Promise<ExportResult> {\n const { registry, project, assets, settings, maxTimeUs, mimeType = 'video/mp4', filename } = options\n const { assets: payloadAssets, timeline, missing } = await buildWorkerPayload(\n registry,\n project,\n assets,\n settings,\n maxTimeUs,\n )\n if (shouldCancel()) throw new ExportCancelledError()\n\n // 缺源文件的素材由 Worker 逐 clip 跳过(不阻断整次导出);\n // 这里只做一次提示,最终原因会出现在 result.skipped 里。\n if (missing.length > 0) {\n console.warn('[export] 以下素材缺少源文件,相关片段将被跳过:', missing.join('、'))\n }\n\n return new Promise<ExportResult>((resolve, reject) => {\n const startedAt = performance.now()\n\n const cleanup = () => {\n worker.onmessage = null\n worker.onerror = null\n worker.terminate()\n }\n\n worker.onmessage = (event: MessageEvent<WorkerResponse>) => {\n const message = event.data\n if (message.type === 'progress') {\n if (shouldCancel()) return\n onProgress(message.progress)\n return\n }\n if (message.type === 'done') {\n cleanup()\n if (shouldCancel()) {\n reject(new ExportCancelledError())\n return\n }\n resolve({\n blob: new Blob([message.buffer], { type: mimeType }),\n filename: filename ?? createExportFilename(),\n mimeType,\n durationUs: computeExportDuration(project, maxTimeUs),\n width: settings.width,\n height: settings.height,\n fps: settings.fps,\n bitrate: settings.bitrate,\n elapsedMs: performance.now() - startedAt,\n skipped: message.skipped ?? [],\n mode: 'worker',\n })\n return\n }\n if (message.type === 'error') {\n cleanup()\n reject(\n message.message === CANCELLED || shouldCancel()\n ? new ExportCancelledError()\n : new Error(message.message),\n )\n }\n }\n\n worker.onerror = (event) => {\n cleanup()\n reject(new Error(event.message || '导出 Worker 异常'))\n }\n\n if (shouldCancel()) {\n cleanup()\n reject(new ExportCancelledError())\n return\n }\n\n worker.postMessage({ type: 'export', assets: payloadAssets, timeline, fixDuration: true })\n })\n}\n\n/**\n * 启动导出。\n *\n * 优先走 Worker(不阻塞 UI);含文字片段或 Worker 不可用 / 失败时回退主线程。\n * `cancel()` 后 promise 以 `ExportCancelledError` reject。\n */\nexport function startExport(\n options: ExportPlanOptions,\n onProgress: (progress: number) => void = () => undefined,\n): CoreExportHandle {\n const { project, assets, settings } = options\n let cancelled = false\n let worker: Worker | null = null\n\n const cancel = () => {\n cancelled = true\n if (worker != null) {\n try {\n worker.postMessage({ type: 'cancel' })\n } catch {\n // Worker 已终止时 postMessage 会抛,忽略即可\n }\n worker.terminate()\n worker = null\n }\n }\n\n const promise = (async (): Promise<ExportResult> => {\n const supported = await checkExportSupport({\n width: settings.width,\n height: settings.height,\n bitrate: settings.bitrate,\n })\n if (!supported) {\n throw new Error(\n `当前设备不支持编码 ${settings.width}×${settings.height} @ ${(settings.bitrate / 1e6).toFixed(1)}Mbps,请降低分辨率或码率`,\n )\n }\n if (cancelled) throw new ExportCancelledError()\n\n const makeWorker = () => {\n const factory = options.createWorker ?? defaultWorkerFactory\n if (factory == null) return null\n try {\n return factory()\n } catch (err) {\n console.warn('[export] 创建 Worker 失败,回退主线程', err)\n return null\n }\n }\n\n if (!canUseWorker(project, assets)) {\n return exportOnMainThread(options, onProgress, () => cancelled)\n }\n\n const created = makeWorker()\n if (created == null) {\n return exportOnMainThread(options, onProgress, () => cancelled)\n }\n\n worker = created\n try {\n return await exportViaWorker(options, created, onProgress, () => cancelled)\n } catch (err) {\n if (cancelled || err instanceof ExportCancelledError) throw new ExportCancelledError()\n console.warn('[export] Worker 导出失败,回退主线程', err)\n return exportOnMainThread(options, onProgress, () => cancelled)\n }\n })()\n\n return { promise, cancel }\n}\n","/**\r\n * 时间轴 clip → sprite / clip 实例的同步器。\r\n *\r\n * 预览层需要把「store 里的纯数据」翻译成 WebAV 实例。同步策略:\r\n * - 新增 clip:从 registry 创建实例 + VisibleSprite;\r\n * - 删除 clip:销毁 sprite 与实例;\r\n * - 属性变化:只更新 sprite 属性(不重建,避免闪烁);\r\n * - 结构变化(入点/时长变化导致需要新 IClip):重建实例。\r\n *\r\n * 这里不含 React,由 preview 的 hook 驱动调用。\r\n */\r\n\r\nimport type { VisibleSprite } from '@webav/av-cliper'\r\nimport type { MediaAsset, Project, TimelineClip, Track } from '../../types'\r\nimport type { ClipRegistry } from './clip-registry'\r\nimport { applyClipToSprite, createVisibleSprite } from './sprite-factory'\r\n\r\nexport interface SpriteSyncResult {\r\n /** timelineClipId → sprite */\r\n sprites: Map<string, VisibleSprite>\r\n removed: string[]\r\n created: string[]\r\n errors: Array<{ clipId: string; message: string }>\r\n}\r\n\r\nfunction zIndexOf(tracks: Track[], trackId: string, clip: TimelineClip): number {\r\n const trackIndex = tracks.findIndex((t) => t.id === trackId)\r\n return Math.max(0, trackIndex) * 1000 + Math.round(clip.startUs / 1_000_000)\r\n}\r\n\r\n/**\r\n * 计算需要重建实例的 clip(入点 / 时长变了,旧 IClip 的数据窗口不再匹配)。\r\n */\r\nfunction needsRebuild(previous: TimelineClip | undefined, next: TimelineClip): boolean {\r\n if (!previous) return true\r\n return previous.inPointUs !== next.inPointUs\r\n}\r\n\r\nexport class SpriteSynchronizer {\r\n /** 由创建方注入,保证一个预览画布只操作自己的 clip 实例 */\r\n private readonly registry: ClipRegistry\r\n private sprites = new Map<string, VisibleSprite>()\r\n /** 记录上一次同步的数据,用于检测属性变化 */\r\n private lastClips = new Map<string, TimelineClip>()\r\n\r\n constructor(registry: ClipRegistry) {\r\n this.registry = registry\r\n }\r\n\r\n getSprite(clipId: string): VisibleSprite | undefined {\r\n return this.sprites.get(clipId)\r\n }\r\n\r\n listSprites(): VisibleSprite[] {\r\n return Array.from(this.sprites.values())\r\n }\r\n\r\n /**\r\n * 全量对齐:以 store 的 clips 为准增删 sprite。\r\n *\r\n * @param assets 素材表(用于判断类型)\r\n * @returns 变更详情,调用方据此把 sprite 挂到 AVCanvas\r\n */\r\n async sync(\r\n project: Project,\r\n assets: MediaAsset[],\r\n handlers: {\r\n onAdd: (sprite: VisibleSprite) => Promise<void>\r\n onRemove: (sprite: VisibleSprite) => void\r\n },\r\n ): Promise<SpriteSyncResult> {\r\n const assetMap = new Map(assets.map((a) => [a.id, a]))\r\n const nextIds = new Set(project.clips.map((c) => c.id))\r\n const removed: string[] = []\r\n\r\n for (const [id, sprite] of Array.from(this.sprites)) {\r\n if (nextIds.has(id)) continue\r\n handlers.onRemove(sprite)\r\n this.sprites.delete(id)\r\n this.lastClips.delete(id)\r\n this.registry.destroyInstance(id)\r\n removed.push(id)\r\n }\r\n\r\n const created: string[] = []\r\n const errors: Array<{ clipId: string; message: string }> = []\r\n\r\n for (const clip of project.clips) {\r\n const track = project.tracks.find((t) => t.id === clip.trackId)\r\n if (!track) continue\r\n const asset = assetMap.get(clip.assetId)\r\n if (!asset) {\r\n errors.push({ clipId: clip.id, message: '引用的素材已不存在,请重新导入' })\r\n continue\r\n }\r\n\r\n // 素材元数据在、但二进制不在(刷新后恢复的工程):\r\n // 跳过而不抛错,由调用方汇总一次友好提示。\r\n const needsFile = asset.kind === 'video' || asset.kind === 'audio' || asset.kind === 'image'\r\n if (needsFile && this.registry.getFile(asset.id) == null) {\r\n errors.push({ clipId: clip.id, message: `素材文件未载入:${asset.name}` })\r\n continue\r\n }\r\n\r\n const previous = this.lastClips.get(clip.id)\r\n const existing = this.sprites.get(clip.id)\r\n\r\n if (existing && !needsRebuild(previous, clip)) {\r\n // 仅更新属性\r\n applyClipToSprite(existing, clip)\r\n existing.zIndex = zIndexOf(project.tracks, clip.trackId, clip)\r\n existing.visible = !track.hidden\r\n this.lastClips.set(clip.id, clip)\r\n continue\r\n }\r\n\r\n if (existing) {\r\n handlers.onRemove(existing)\r\n this.sprites.delete(clip.id)\r\n this.registry.destroyInstance(clip.id)\r\n }\r\n\r\n try {\r\n // 视频 / 音频轨上的音频 clip 需要独立 IClip;文字/字幕按内容重建\r\n const instance = await this.registry.createInstance({\r\n timelineClipId: clip.id,\r\n assetId: clip.assetId,\r\n inPointUs: clip.inPointUs,\r\n durationUs: clip.durationUs * clip.playbackRate,\r\n text: clip.text,\r\n textStyle: clip.textStyle,\r\n srt: clip.srt,\r\n subtitleStyle: clip.subtitleStyle,\r\n projectWidth: project.width,\r\n projectHeight: project.height,\r\n })\r\n\r\n // 音频轨只提供声音,不绘制画面\r\n const spriteSource = track.kind === 'audio' ? instance : instance\r\n const sprite = createVisibleSprite(spriteSource, clip, {\r\n zIndex: zIndexOf(project.tracks, clip.trackId, clip),\r\n })\r\n sprite.visible = !track.hidden\r\n if (track.kind === 'audio' && asset.kind === 'audio') {\r\n // 纯音频素材没有画面:尺寸置 0,避免 AVCanvas 绘制空内容\r\n sprite.rect.w = 0\r\n sprite.rect.h = 0\r\n }\r\n\r\n await handlers.onAdd(sprite)\r\n this.sprites.set(clip.id, sprite)\r\n this.lastClips.set(clip.id, clip)\r\n created.push(clip.id)\r\n } catch (err) {\r\n errors.push({\r\n clipId: clip.id,\r\n message: err instanceof Error ? err.message : String(err),\r\n })\r\n }\r\n }\r\n\r\n return { sprites: new Map(this.sprites), removed, created, errors }\r\n }\r\n\r\n /** 清空所有 sprite(销毁实例) */\r\n clear(handlers: { onRemove: (sprite: VisibleSprite) => void }): void {\r\n for (const [id, sprite] of this.sprites) {\r\n handlers.onRemove(sprite)\r\n this.registry.destroyInstance(id)\r\n }\r\n this.sprites.clear()\r\n this.lastClips.clear()\r\n }\r\n\r\n stats(): number {\r\n return this.sprites.size\r\n }\r\n}\r\n","/**\r\n * AVCanvas 的封装层(feature 与 WebAV 之间的唯一桥梁)。\r\n *\r\n * 目的:把 WebAV 的实例管理、sprite 同步、事件订阅全部收拢到 core,\r\n * 使 features / hooks 不直接依赖 `@webav/*`(分层规则要求)。\r\n *\r\n * 关键设计:\r\n * - 本类 **不** 调用 `av.play()`。AVCanvas 自带 33ms 步进与自己的 AudioContext,\r\n * 与我们的 PlaybackClock 会打架;播放统一由外层 `previewFrame` 驱动。\r\n * - 播放时画面靠「每帧 preFrame + 重绘」推进;AVCanvas 的内部渲染循环在暂停态\r\n * 仍会按当前时间持续重绘,这正是我们要的(属性变化立刻反映)。\r\n * - **不使用 AVCanvas 内建的选中框与拖拽**。内建方案有三个硬伤:\r\n * 1. 选框样式只能靠 `!important` 覆盖它的私有 DOM 结构,脆弱;\r\n * 2. 每次 `propsChange` 都要读 `getBoundingClientRect()` 并写 style,拖动时与\r\n * React 重渲染抢主线程(实测单次拖动 6 个 ~100ms 的 long task);\r\n * 3. 拖动只改 sprite、不回写 store,必须额外搭一条回写链路才能持久化。\r\n * 因此把 sprite 设为 `interactable = 'disabled'`:它不参与内建选框与拖拽,\r\n * **但命中测试仍然有效**(AVCanvas 的 `activeSprite` setter 会拒绝 disabled,\r\n * 而命中测试走 `rect.checkHit`,不受影响)。feature 层据此用 DOM 自绘选框。\r\n * - `destroy()` 会移除全部 sprite 并释放实例。\r\n */\r\n\r\nimport { AVCanvas } from '@webav/av-canvas'\r\nimport type { VisibleSprite } from '@webav/av-cliper'\r\nimport type { ClipTransform, MediaAsset, Project } from '../../types'\r\nimport { pointInTransform } from '../../timeline/geometry'\r\nimport { SpriteSynchronizer } from './sprite-sync'\r\nimport type { ClipRegistry } from './clip-registry'\r\n\r\nexport interface PreviewCanvasOptions {\r\n width: number\r\n height: number\r\n bgColor?: string\r\n /** clip 实例注册表;每个编辑器实例一份,不能共享 */\r\n registry: ClipRegistry\r\n}\r\n\r\nexport interface SyncReport {\r\n created: string[]\r\n removed: string[]\r\n errors: Array<{ clipId: string; message: string }>\r\n}\r\n\r\nexport class PreviewCanvas {\r\n private canvas: AVCanvas\r\n private host: HTMLElement\r\n private synchronizer: SpriteSynchronizer\r\n private clipToSprite = new Map<string, VisibleSprite>()\r\n private destroyed = false\r\n private lastPreviewUs = -1\r\n /** 工程分辨率(命中测试的坐标系) */\r\n private projectWidth: number\r\n private projectHeight: number\r\n\r\n constructor(host: HTMLElement, opts: PreviewCanvasOptions) {\r\n this.host = host\r\n this.projectWidth = opts.width\r\n this.projectHeight = opts.height\r\n this.synchronizer = new SpriteSynchronizer(opts.registry)\r\n this.canvas = new AVCanvas(host, {\r\n width: opts.width,\r\n height: opts.height,\r\n bgColor: opts.bgColor ?? '#000000',\r\n })\r\n }\r\n\r\n get root(): AVCanvas {\r\n return this.canvas\r\n }\r\n\r\n /** 画布 element 的 CSS 尺寸与工程分辨率之比,供外层换算指针坐标 */\r\n getCssScale(): { x: number; y: number } {\r\n const rect = this.host.getBoundingClientRect()\r\n return {\r\n x: this.projectWidth > 0 && rect.width > 0 ? rect.width / this.projectWidth : 1,\r\n y: this.projectHeight > 0 && rect.height > 0 ? rect.height / this.projectHeight : 1,\r\n }\r\n }\r\n\r\n /** 与 store 数据对齐 sprite。\r\n *\r\n * @returns 变更与错误清单(单个 clip 失败不应中断整体同步)\r\n */\r\n async sync(project: Project, assets: MediaAsset[]): Promise<SyncReport> {\r\n if (this.destroyed) return { created: [], removed: [], errors: [] }\r\n this.projectWidth = project.width\r\n this.projectHeight = project.height\r\n const result = await this.synchronizer.sync(project, assets, {\r\n onAdd: async (sprite) => {\r\n await this.canvas.addSprite(sprite)\r\n },\r\n onRemove: (sprite) => {\r\n this.canvas.removeSprite(sprite)\r\n },\r\n })\r\n this.clipToSprite.clear()\r\n for (const clip of project.clips) {\r\n const sprite = this.synchronizer.getSprite(clip.id)\r\n if (sprite == null) continue\r\n this.clipToSprite.set(clip.id, sprite)\r\n }\r\n return { created: result.created, removed: result.removed, errors: result.errors }\r\n }\r\n\r\n /**\r\n * 把 store 里的「纯属性」(位置/尺寸/旋转/透明度/翻转)写回已有 sprite。\r\n *\r\n * 不重建 IClip,只改 sprite 字段 —— 属性面板拖动时高频调用,必须便宜。\r\n * sprite 的 rect 是普通对象(只在 setter 里比对新旧值),因此重复写入同值\r\n * 不会产生任何额外开销,也不需要再临时断开事件(本类已不再监听 propsChange)。\r\n */\r\n applyProps(project: Project): void {\r\n if (this.destroyed) return\r\n for (const clip of project.clips) {\r\n const sprite = this.clipToSprite.get(clip.id) ?? this.synchronizer.getSprite(clip.id)\r\n if (sprite == null) continue\r\n sprite.rect.x = clip.transform.x\r\n sprite.rect.y = clip.transform.y\r\n sprite.rect.w = clip.transform.w\r\n sprite.rect.h = clip.transform.h\r\n sprite.rect.angle = (clip.transform.angle * Math.PI) / 180\r\n sprite.opacity = clip.opacity\r\n sprite.flip = clip.flip\r\n }\r\n }\r\n\r\n /**\r\n * 只把「单个 clip」的几何写到 sprite,并立即重绘当前帧。\r\n *\r\n * 用于画布内拖动/缩放的实时预览:拖动过程中不写 store(避免 React 树与\r\n * 历史快照跟着每帧重建),只在这里直接改 sprite,松手时再提交一次。\r\n */\r\n async previewTransform(clipId: string, transform: ClipTransform): Promise<void> {\r\n if (this.destroyed) return\r\n const sprite = this.clipToSprite.get(clipId) ?? this.synchronizer.getSprite(clipId)\r\n if (sprite == null) return\r\n sprite.rect.x = transform.x\r\n sprite.rect.y = transform.y\r\n sprite.rect.w = transform.w\r\n sprite.rect.h = transform.h\r\n sprite.rect.angle = (transform.angle * Math.PI) / 180\r\n await this.redrawCurrentFrame()\r\n }\r\n\r\n /**\r\n * 渲染某一时刻。\r\n *\r\n * 同一个时间点重复调用会被跳过(AVCanvas.previewFrame 会完整走一遍 preFrame,\r\n * 每帧调用代价很高)。播放循环里时间必然递增,因此不会误伤。\r\n */\r\n async previewFrame(us: number): Promise<void> {\r\n if (this.destroyed) return\r\n const time = Math.max(0, Math.round(us))\r\n if (time === this.lastPreviewUs) return\r\n this.lastPreviewUs = time\r\n await this.canvas.previewFrame(time)\r\n }\r\n\r\n /** 强制重绘当前时刻(属性变化后调用) */\r\n async refresh(us: number): Promise<void> {\r\n this.lastPreviewUs = -1\r\n await this.previewFrame(us)\r\n }\r\n\r\n /** 重绘「上一次渲染的时间点」,用于画布内交互的即时反馈 */\r\n private async redrawCurrentFrame(): Promise<void> {\r\n const us = this.lastPreviewUs < 0 ? 0 : this.lastPreviewUs\r\n this.lastPreviewUs = -1\r\n await this.previewFrame(us)\r\n }\r\n\r\n /** 当前帧截图(base64 data URL) */\r\n captureImage(): string | null {\r\n if (this.destroyed) return null\r\n try {\r\n return this.canvas.captureImage()\r\n } catch {\r\n return null\r\n }\r\n }\r\n\r\n /**\r\n * 命中测试:返回最上层的可交互 clip。\r\n *\r\n * 为什么自己算而不直接用 AVCanvas 的 `activeSpriteByCoord`:\r\n * 那边只返回一个 sprite,调用方拿不到几何信息;且它按 `getSprites()` 的数组\r\n * 反序取第一个命中,语义与「轨道顺序即 zIndex」一致 —— 这里保持同样的顺序,\r\n * 但用 `clip.transform` 做旋转矩形命中,避免依赖 sprite 的内部状态。\r\n *\r\n * ⚠️ **必须校验播放头是否落在片段区间内**。\r\n * `sprite.visible` 只反映「轨道是否被隐藏」(sync 时由 `track.hidden` 决定),\r\n * 与时间无关;AVCanvas 的时间可见性只影响绘制,不会让 sprite 不可命中。\r\n * 若这里不判时间,播放头不在该片段上时(画面里根本看不到它),用户仍然能\r\n * 在它原来的位置点中并拖动它 —— 看起来像「凭空拖动了一块空白区域」。\r\n *\r\n * @param point 工程坐标\r\n * @param project 用于取 zIndex 顺序\r\n * @param atUs 当前时间(播放头),判断哪些 clip 此刻真实可见\r\n */\r\n hitTest(point: { x: number; y: number }, project: Project, atUs: number): string | null {\r\n if (this.destroyed) return null\r\n // 轨道顺序即层级:数组越靠后越在上层\r\n const order = new Map(project.tracks.map((track, index) => [track.id, index]))\r\n const candidates = project.clips\r\n .filter((clip) => {\r\n const sprite = this.clipToSprite.get(clip.id)\r\n if (sprite == null || !sprite.visible) return false\r\n // 纯音频 clip 的 transform 是「铺满画布」的占位值,但 sprite 尺寸为 0:\r\n // 若用 transform 判定,点画布任意位置都会选中音频片段。\r\n if (sprite.rect.w <= 0 || sprite.rect.h <= 0) return false\r\n // 只有播放头落在 [start, end] 内的片段此刻才在画面上\r\n if (atUs < clip.startUs || atUs > clip.startUs + clip.durationUs) return false\r\n return pointInTransform(point, clip.transform)\r\n })\r\n .sort((a, b) => {\r\n const za = order.get(a.trackId) ?? 0\r\n const zb = order.get(b.trackId) ?? 0\r\n if (za !== zb) return za - zb\r\n return a.startUs - b.startUs\r\n })\r\n const top = candidates[candidates.length - 1]\r\n return top?.id ?? null\r\n }\r\n\r\n /** 取某 clip 当前 sprite 上的实际几何(画布内拖动开始时作为快照) */\r\n getSpriteTransform(clipId: string): ClipTransform | null {\r\n const sprite = this.clipToSprite.get(clipId) ?? this.synchronizer.getSprite(clipId)\r\n if (sprite == null) return null\r\n const rect = sprite.rect\r\n return {\r\n x: rect.x,\r\n y: rect.y,\r\n w: rect.w,\r\n h: rect.h,\r\n angle: (rect.angle * 180) / Math.PI,\r\n }\r\n }\r\n\r\n /** 合成媒体流(录制 Lab 用) */\r\n captureStream(): MediaStream {\r\n return this.canvas.captureStream()\r\n }\r\n\r\n destroy(): void {\r\n if (this.destroyed) return\r\n this.destroyed = true\r\n this.clipToSprite.clear()\r\n this.synchronizer.clear({\r\n onRemove: (sprite) => {\r\n try {\r\n this.canvas.removeSprite(sprite)\r\n } catch {\r\n // ignore\r\n }\r\n },\r\n })\r\n try {\r\n this.canvas.destroy()\r\n } catch {\r\n // ignore\r\n }\r\n }\r\n}\r\n","/**\n * 修正 WebAV 输出的 fMP4 时长。\n *\n * WebAV 输出的是分片 MP4(moof/mdat),部分播放器无法从 moov 中读出总时长。\n * `fixFMP4Duration` 会重写 moov 的 duration 字段。\n */\n\nimport { fixFMP4Duration } from '@webav/av-cliper'\n\n/**\n * @param blob 原始 MP4\n * @returns 修正后的 MP4(同一 MIME)\n */\nexport async function fixFMP4DurationStream(blob: Blob): Promise<Blob> {\n const stream = blob.stream() as ReadableStream<Uint8Array>\n const fixed = await fixFMP4Duration(stream)\n return new Blob([await new Response(fixed).arrayBuffer()], { type: 'video/mp4' })\n}\n","/**\n * 自定义 IClip:把已解码好的 PCM 数据当作音频素材使用。\n *\n * 为什么需要它:实测 `@webav/av-cliper@1.2.8` 的 `AudioClip` 构造函数无论传流\n * 还是传 `Float32Array[]`,内部都会执行 `new AudioContext({ sampleRate: 48000 })`,\n * 而 Web Worker 里既没有 `AudioContext` 也没有 `OfflineAudioContext` —— 因此\n * **AudioClip 无法在 Worker 中构造**(导出 Worker 会直接抛 \"AudioContext is not defined\")。\n *\n * 取舍:主线程用官方 `AudioClip` 解码一次(保证解码/重采样由 WebAV 负责),\n * 把 48kHz 双声道 PCM 传进 Worker,Worker 用本类提供数据。\n *\n * tick 语义严格对齐 `AudioClip.tick`:返回「距上次 tick 的增量」;\n * 时间倒退或跨度 > 3s 时重置游标(与 WebAV 文档一致)。\n */\n\nimport type { IClip } from '@webav/av-cliper'\nimport type { ClipMetaBase } from './meta'\n\nexport interface PcmClipOptions {\n /** 采样率,默认 48000 */\n sampleRate?: number\n}\n\nexport class PcmClip implements IClip {\n readonly ready: Promise<ClipMetaBase>\n private channels: Float32Array[]\n private sampleRate: number\n private durationUs: number\n /** 上次 tick 的源时间(微秒) */\n private lastUs = 0\n /** 上次取到的采样帧位置 */\n private cursor = 0\n private destroyed = false\n\n constructor(pcm: Float32Array[], opts: PcmClipOptions = {}) {\n this.channels = pcm.length > 0 ? pcm : [new Float32Array(0)]\n this.sampleRate = opts.sampleRate ?? 48_000\n const frames = this.channels[0]?.length ?? 0\n this.durationUs = Math.round((frames / this.sampleRate) * 1_000_000)\n this.ready = Promise.resolve(this.meta)\n }\n\n get meta(): ClipMetaBase {\n // 音频没有画面,宽高为 0(与 AudioClip 一致)\n return { width: 0, height: 0, duration: this.durationUs }\n }\n\n async tick(time: number): Promise<{\n audio?: Float32Array[]\n state: 'done' | 'success'\n }> {\n if (this.destroyed) return { audio: [], state: 'done' }\n if (time >= this.durationUs) return { audio: [], state: 'done' }\n\n const deltaUs = time - this.lastUs\n // 与 AudioClip 一致:倒退或跨度超过 3s 时重置游标(音频会短暂中断)\n if (time < this.lastUs || deltaUs > 3_000_000) {\n this.lastUs = time\n this.cursor = Math.ceil((time / 1_000_000) * this.sampleRate)\n return { audio: [new Float32Array(0)], state: 'success' }\n }\n\n this.lastUs = time\n const frames = Math.ceil((deltaUs / 1_000_000) * this.sampleRate)\n const nextCursor = this.cursor + frames\n const slices = this.channels.map((channel) => channel.slice(this.cursor, nextCursor))\n this.cursor = nextCursor\n return { audio: slices, state: 'success' }\n }\n\n async clone(): Promise<this> {\n // 共享同一份 PCM(只读),但游标独立\n const copy = new PcmClip(this.channels, { sampleRate: this.sampleRate })\n return copy as this\n }\n\n async split(time: number): Promise<[this, this]> {\n if (time <= 0 || time >= this.durationUs) throw new Error('\"time\" out of bounds')\n const frame = Math.floor((time / 1_000_000) * this.sampleRate)\n const left = new PcmClip(\n this.channels.map((c) => c.slice(0, frame)),\n { sampleRate: this.sampleRate },\n )\n const right = new PcmClip(\n this.channels.map((c) => c.slice(frame)),\n { sampleRate: this.sampleRate },\n )\n await Promise.all([left.ready, right.ready])\n return [left as this, right as this]\n }\n\n destroy(): void {\n this.destroyed = true\n this.channels = []\n }\n}\n","/**\r\n * 导出 Worker 的运行时逻辑(WebAV 调用集中在此)。\r\n *\r\n * `features/export/export.worker.ts` 只是薄入口,避免在 features 层直接 import WebAV。\r\n *\r\n * 为什么必须放 Worker:`Combinator.output()` 在当前线程同步步进编码,\r\n * 主线程执行会冻结 UI(30s 1080p 通常需要数十秒)。\r\n *\r\n * 已知限制(实测):\r\n * - 文字素材依赖 `document`(renderTxt2ImgBitmap 内部 createElement + SVG foreignObject),\r\n * Worker 内无法重建。调用方在检测到文字片段时应回退到主线程导出;\r\n * - 音频素材的 `AudioClip` 构造依赖 `AudioContext.decodeAudioData`,Worker 内不存在该 API,\r\n * 因此必须由主线程预解码 PCM 后传入(见 WorkerAssetPayload.pcm);\r\n * - `MediaStreamClip` 依赖实时时间流,不能放进 Combinator,任何线程都不支持。\r\n */\r\n\r\nimport {\r\n Combinator,\r\n EmbedSubtitlesClip,\r\n ImgClip,\r\n MP4Clip,\r\n OffscreenSprite,\r\n fixFMP4Duration,\r\n} from '@webav/av-cliper'\r\nimport type { IClip } from '@webav/av-cliper'\r\nimport type {\r\n WorkerAssetPayload,\r\n WorkerRequest,\r\n WorkerResponse,\r\n WorkerTimelinePayload,\r\n} from './types'\r\nimport { GainClip } from './gain-clip'\r\nimport { PcmClip } from './pcm-clip'\nimport { mapTransformToOutput } from './coordinate'\r\nimport type { TimelineClip } from '../../types'\r\n\r\n/** 文字素材需要 DOM,Worker 内无法重建;调用方应在派发前过滤 */\r\nexport class WorkerUnsupportedClipError extends Error {\r\n constructor(message: string) {\r\n super(message)\r\n this.name = 'WorkerUnsupportedClipError'\r\n }\r\n}\r\n\r\nlet cancelled = false\r\n\r\nfunction toStream(blob: Blob): ReadableStream<Uint8Array> {\r\n return blob.stream() as ReadableStream<Uint8Array>\r\n}\r\n\r\n/** 音量 / 淡入淡出包装(与主线程 exporter.withClipGain 保持一致的语义) */\r\nfunction withGain(clip: IClip, timeline: TimelineClip): IClip {\r\n if (timeline.volume === 1 && timeline.fadeInUs === 0 && timeline.fadeOutUs === 0) return clip\r\n return new GainClip(clip, {\r\n volume: timeline.volume,\r\n fadeInUs: timeline.fadeInUs,\r\n fadeOutUs: timeline.fadeOutUs,\r\n durationUs: timeline.durationUs,\r\n playbackRate: timeline.playbackRate,\r\n })\r\n}\r\n\r\n/** Worker 内按素材类型重建 IClip(与主线程 clip-factory 对齐,但不含 DOM 依赖的类型) */\r\nexport async function buildClipInWorker(\r\n asset: WorkerAssetPayload,\r\n): Promise<IClip> {\r\n switch (asset.kind) {\r\n case 'video': {\r\n if (asset.file == null) throw new Error(`素材文件缺失:${asset.id}`)\r\n const clip = new MP4Clip(toStream(asset.file))\r\n await clip.ready\r\n return clip\r\n }\r\n case 'audio': {\r\n // ⚠️ 实测:`AudioClip` 构造函数(无论传流还是 Float32Array[])内部都会\r\n // `new AudioContext(...)`,Worker 中没有该 API,必然抛 \"AudioContext is not defined\"。\r\n // 因此主线程预解码 PCM,Worker 侧用不依赖 AudioContext 的 PcmClip 提供数据。\r\n if (asset.pcm == null || asset.pcm.length === 0) {\r\n throw new Error(`音频素材缺少 PCM 数据:${asset.id}`)\r\n }\r\n const clip = new PcmClip(asset.pcm)\r\n await clip.ready\r\n return clip\r\n }\r\n case 'image': {\r\n if (asset.file == null) throw new Error(`图片文件缺失:${asset.id}`)\r\n const clip = new ImgClip(toStream(asset.file))\r\n await clip.ready\r\n return clip\r\n }\r\n case 'subtitle': {\r\n if (!asset.srt) throw new Error('字幕内容缺失')\r\n const clip = new EmbedSubtitlesClip(asset.srt, {\r\n videoWidth: asset.width,\r\n videoHeight: asset.height,\r\n color: asset.subtitleStyle?.color,\r\n fontFamily: asset.subtitleStyle?.fontFamily,\r\n fontSize: asset.subtitleStyle?.fontSize,\r\n strokeStyle: asset.subtitleStyle?.strokeStyle,\r\n lineWidth: asset.subtitleStyle?.lineWidth,\r\n bottomOffset: asset.subtitleStyle?.bottomOffset,\r\n textBgColor: asset.subtitleStyle?.textBgColor,\r\n })\r\n await clip.ready\r\n return clip\r\n }\r\n case 'text':\r\n throw new WorkerUnsupportedClipError(\r\n '文字素材依赖 DOM 渲染,无法在 Worker 内重建(请使用主线程导出)',\r\n )\r\n default:\r\n throw new Error(`未知素材类型:${String(asset.kind)}`)\r\n }\r\n}\r\n\r\nexport interface WorkerExportResult {\r\n buffer: ArrayBuffer\r\n skipped: Array<{ clipId: string; reason: string }>\r\n}\r\n\r\n/**\r\n * 在 Worker 内执行完整导出。\r\n *\r\n * @param assets 素材快照(File 通过结构化克隆传入,不复制二进制)\r\n * @param timeline 时间轴纯数据\r\n * @param onProgress 0~1 进度\r\n * @param shouldCancel 轮询式取消检查\r\n */\r\nexport async function runExportInWorker(\r\n assets: WorkerAssetPayload[],\r\n timeline: WorkerTimelinePayload,\r\n onProgress: (progress: number) => void,\r\n shouldCancel: () => boolean,\r\n): Promise<WorkerExportResult> {\r\n const assetMap = new Map(assets.map((a) => [a.id, a]))\r\n const combinator = new Combinator({\r\n width: timeline.width,\r\n height: timeline.height,\r\n fps: timeline.fps,\r\n bitrate: timeline.bitrate,\r\n bgColor: timeline.bgColor,\r\n audio: timeline.includeAudio ? undefined : false,\r\n })\r\n\r\n const clipCache = new Map<string, IClip>()\r\n const skipped: Array<{ clipId: string; reason: string }> = []\r\n // 工程坐标(clip.transform 的基准)与输出坐标\r\n const sourceSize = {\r\n width: timeline.projectWidth ?? timeline.width,\r\n height: timeline.projectHeight ?? timeline.height,\r\n }\r\n const targetSize = { width: timeline.width, height: timeline.height }\r\n let maxTimeUs = 0\r\n let zIndex = 0\r\n\r\n const abortIfNeeded = () => {\r\n if (shouldCancel()) throw new Error('__cancelled__')\r\n }\r\n\r\n try {\r\n const ordered = [...timeline.clips].sort((a, b) => {\r\n const aAudio = a.kind === 'audio' ? 1 : 0\r\n const bAudio = b.kind === 'audio' ? 1 : 0\r\n if (aAudio !== bAudio) return aAudio - bAudio\r\n return a.clip.startUs - b.clip.startUs\r\n })\r\n\r\n for (const item of ordered) {\r\n abortIfNeeded()\r\n const asset = assetMap.get(item.clip.assetId)\r\n if (!asset) {\r\n skipped.push({ clipId: item.clip.id, reason: '素材缺失' })\r\n continue\r\n }\r\n if (item.clip.muted) continue\r\n\r\n let clip = clipCache.get(asset.id)\r\n if (clip == null) {\r\n try {\r\n clip = await buildClipInWorker(asset)\r\n clipCache.set(asset.id, clip)\r\n } catch (err) {\r\n const reason = err instanceof Error ? err.message : String(err)\r\n console.warn('[export.worker] clip skipped:', item.clip.id, reason)\r\n skipped.push({ clipId: item.clip.id, reason })\r\n continue\r\n }\r\n }\r\n\r\n // 每个时间轴 clip 独立套音量包络(Combinator 内部会 clone sprite → clone clip)\r\n const tc: TimelineClip = item.clip\r\n const sprite = new OffscreenSprite(withGain(clip, tc))\r\n // 工程坐标 → 输出坐标(导出分辨率可不同于工程分辨率,不换算会挤在左上角)\r\n const mapped = mapTransformToOutput(tc.transform, sourceSize, targetSize)\r\n sprite.rect.x = mapped.x\r\n sprite.rect.y = mapped.y\r\n sprite.rect.w = mapped.w\r\n sprite.rect.h = mapped.h\r\n sprite.rect.angle = (mapped.angle * Math.PI) / 180\r\n sprite.opacity = tc.opacity\r\n sprite.flip = tc.flip\r\n sprite.zIndex = zIndex\r\n sprite.time = {\r\n offset: tc.startUs,\r\n duration: tc.durationUs,\r\n playbackRate: tc.playbackRate,\r\n }\r\n zIndex += 1\r\n maxTimeUs = Math.max(maxTimeUs, tc.startUs + tc.durationUs)\r\n\r\n await combinator.addSprite(sprite, { main: false })\r\n }\r\n\r\n if (maxTimeUs <= 0) throw new Error('时间轴为空,无法导出')\r\n\r\n combinator.on('OutputProgress', (progress) => onProgress(progress))\r\n\r\n const stream = combinator.output({ maxTime: timeline.maxTimeUs ?? maxTimeUs })\r\n const raw = await new Response(stream).arrayBuffer()\r\n abortIfNeeded()\r\n\r\n let buffer = raw\r\n try {\r\n const fixed = await fixFMP4Duration(\r\n new Response(raw).body as ReadableStream<Uint8Array>,\r\n )\r\n buffer = await new Response(fixed).arrayBuffer()\r\n } catch (err) {\r\n console.warn('[export-worker] fixFMP4Duration failed, fallback to raw', err)\r\n }\r\n\r\n return { buffer, skipped }\r\n } finally {\r\n for (const clip of clipCache.values()) {\r\n try {\r\n clip.destroy()\r\n } catch {\r\n // ignore\r\n }\r\n }\r\n clipCache.clear()\r\n try {\r\n combinator.destroy()\r\n } catch {\r\n // ignore\r\n }\r\n }\r\n}\r\n\r\n/** Worker 消息入口:由薄入口文件绑定到 self.onmessage */\r\nexport function handleWorkerMessage(event: MessageEvent<WorkerRequest>): void {\r\n const post = (message: WorkerResponse, transfer?: Transferable[]) => {\r\n const scope = self as unknown as { postMessage: (m: WorkerResponse, t?: Transferable[]) => void }\r\n scope.postMessage(message, transfer ?? [])\r\n }\r\n\r\n const message = event.data\r\n if (message.type === 'cancel') {\r\n cancelled = true\r\n return\r\n }\r\n if (message.type !== 'export') return\r\n\r\n cancelled = false\r\n runExportInWorker(\r\n message.assets,\r\n message.timeline,\r\n (progress) => {\r\n if (!cancelled) post({ type: 'progress', progress })\r\n },\r\n () => cancelled,\r\n )\r\n .then(({ buffer, skipped }) => {\r\n post({ type: 'done', buffer, elapsedMs: 0, skipped }, [buffer])\r\n })\r\n .catch((err: unknown) => {\r\n const msg = err instanceof Error ? err.message : String(err)\r\n post({ type: 'error', message: msg === '__cancelled__' ? '导出已取消' : msg })\r\n })\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["renderTxt2ImgBitmap","MP4Clip","fileToStream","AudioClip","ImgClip","DEFAULT_STILL_DURATION_US","EmbedSubtitlesClip","bufferToStream","createWebBridge","createMemoryStorage","createEmptyProject","createEventBus","createHistoryStore","createEditorStore","serializeProject","deserializeProject","getProjectDuration","resolveExportSettings","createExportFilename","ExportCancelledError","detectEnvironment","detectFileType","createId","asUs","generateThumbnails","computeWaveform","AudioClip","fileToStream","detectFileType","UnsupportedFileError","asUs","VisibleSprite","OffscreenSprite","Combinator","clampStillDuration","fixFMP4Duration","ExportCancelledError","createExportFilename","AVCanvas","pointInTransform","fixFMP4Duration","MP4Clip","ImgClip","EmbedSubtitlesClip","Combinator","OffscreenSprite","fixFMP4Duration"],"sources":["../../src/engine/webav/styles.ts","../../src/engine/webav/text-clip.ts","../../src/engine/webav/clip-factory.ts","../../src/engine/webav/offset-clip.ts","../../src/engine/webav/clip-registry.ts","../../src/engine/clock/playback-clock.ts","../../src/engine/clock/preview-audio.ts","../../src/editor/runtime.ts","../../src/lib/error.ts","../../src/editor/create-editor.ts","../../src/engine/webav/import-pipeline.ts","../../src/engine/webav/import-files.ts","../../src/engine/webav/sprite-factory.ts","../../src/engine/webav/gain-clip.ts","../../src/engine/webav/coordinate.ts","../../src/engine/webav/exporter.ts","../../src/engine/webav/export-runner.ts","../../src/engine/webav/sprite-sync.ts","../../src/engine/webav/preview-canvas.ts","../../src/engine/webav/fix-duration.ts","../../src/engine/webav/pcm-clip.ts","../../src/engine/webav/export-worker-runtime.ts"],"sourcesContent":["/**\r\n * 文字 / 字幕的默认样式。抽成独立模块,避免 clip-factory 与 text-clip 相互依赖样式常量。\r\n */\r\n\r\nimport type { SubtitleStyle, TextStyle } from '../../types'\r\n\r\nexport const DEFAULT_TEXT_STYLE: TextStyle = {\r\n fontSize: 64,\r\n color: '#ffffff',\r\n fontFamily: 'sans-serif',\r\n fontWeight: 600,\r\n fontStyle: 'normal',\r\n strokeColor: '#000000',\r\n strokeWidth: 0,\r\n shadowColor: 'rgba(0,0,0,0.6)',\r\n shadowBlur: 8,\r\n align: 'center',\r\n background: 'transparent',\r\n}\r\n\r\n/**\r\n * 字幕默认字体。\r\n *\r\n * ⚠️ 不要写 'Noto Sans SC' —— 大多数系统并未安装该字体,`EmbedSubtitlesClip` 会把\r\n * `ctx.font` 设为 `30px Noto Sans SC`,此时浏览器回退到默认字体,中文可能显示为方框。\r\n * 使用系统字体栈(sans-serif)由浏览器挑选可用的中文字体,最稳。\r\n */\r\nexport const DEFAULT_SUBTITLE_STYLE: SubtitleStyle = {\r\n color: '#ffffff',\r\n textBgColor: null,\r\n fontFamily: 'sans-serif',\r\n fontSize: 30,\r\n strokeStyle: '#000000',\r\n lineWidth: null,\r\n bottomOffset: 30,\r\n}\r\n","/**\r\n * 自定义 IClip:把一段文字渲染成逐帧位图,实现动画标题。\r\n *\r\n * 之所以需要自研而不是直接用 ImgClip:\r\n * - ImgClip 的静态图 `meta.duration === Infinity`,Combinator 无法推断结束时间;\r\n * - 标题常常需要「打字机 / 闪烁」这类只跟时间有关的动画,用 tick 直接生成最直观。\r\n *\r\n * 单位约定:所有时间参数为微秒。\r\n */\r\n\r\nimport type { IClip } from '@webav/av-cliper'\r\nimport type { ClipMetaBase as IClipMeta } from './meta'\r\nimport { renderTxt2ImgBitmap } from '@webav/av-cliper'\r\nimport type { TextStyle } from '../../types'\r\nimport { DEFAULT_TEXT_STYLE } from './styles'\r\n\r\nexport type TextAnimation = 'none' | 'typewriter' | 'blink'\r\n\r\nexport interface TextClipOptions {\r\n text: string\r\n style: TextStyle\r\n width: number\r\n height: number\r\n durationUs: number\r\n animation?: TextAnimation\r\n /** 打字机动画每秒显示的字数 */\r\n charsPerSecond?: number\r\n /**\r\n * 动画相位偏移(微秒)。split 之后右半段需要从分割点继续动画,\r\n * 直接复用原 duration 会让动画重头播放。\r\n */\r\n animOffsetUs?: number\r\n}\r\n\r\nfunction cssFromStyle(style: TextStyle): string {\r\n const shadow = style.shadowBlur > 0\r\n ? `text-shadow: 0 2px ${style.shadowBlur}px ${style.shadowColor};`\r\n : ''\r\n const stroke = style.strokeWidth > 0\r\n ? `-webkit-text-stroke: ${style.strokeWidth}px ${style.strokeColor};`\r\n : ''\r\n return [\r\n `font-size:${style.fontSize}px`,\r\n `color:${style.color}`,\r\n `font-family:${style.fontFamily}`,\r\n `font-weight:${style.fontWeight}`,\r\n `font-style:${style.fontStyle}`,\r\n `text-align:${style.align}`,\r\n `background:${style.background}`,\r\n 'line-height:1.25',\r\n 'padding:8px 16px',\r\n 'white-space:pre-wrap',\r\n 'max-width:80%',\r\n stroke,\r\n shadow,\r\n ]\r\n .filter(Boolean)\r\n .join(';')\r\n}\r\n\r\n/**\r\n * 文字素材。内部缓存最近一次渲染的 ImageBitmap,按「文本 + 样式」做键避免重复渲染。\r\n */\r\nexport class TextClip implements IClip {\r\n readonly ready: Promise<IClipMeta>\r\n private opts: Required<Omit<TextClipOptions, 'animation' | 'charsPerSecond' | 'animOffsetUs'>> & {\r\n animation: TextAnimation\r\n charsPerSecond: number\r\n animOffsetUs: number\r\n }\r\n private metaValue: IClipMeta\r\n private cacheKey = ''\r\n private cachedBitmap: ImageBitmap | null = null\r\n private destroyed = false\r\n\r\n constructor(options: TextClipOptions) {\r\n this.opts = {\r\n ...options,\r\n style: { ...DEFAULT_TEXT_STYLE, ...options.style },\r\n animation: options.animation ?? 'none',\r\n charsPerSecond: options.charsPerSecond ?? 12,\r\n animOffsetUs: options.animOffsetUs ?? 0,\r\n }\r\n this.metaValue = {\r\n width: options.width,\r\n height: options.height,\r\n duration: Math.max(1, options.durationUs),\r\n }\r\n this.ready = Promise.resolve(this.metaValue)\r\n }\r\n\r\n get meta(): IClipMeta {\r\n return this.metaValue\r\n }\r\n\r\n private visibleTextAt(timeUs: number): string | null {\r\n const { text, animation, charsPerSecond } = this.opts\r\n if (animation === 'none') return text\r\n const phaseUs = timeUs + this.opts.animOffsetUs\r\n if (animation === 'typewriter') {\r\n const visible = Math.floor((phaseUs / 1_000_000) * charsPerSecond)\r\n if (visible <= 0) return null\r\n return text.slice(0, visible)\r\n }\r\n // blink:以 1Hz 闪烁\r\n const phase = Math.floor((phaseUs / 1_000_000) * 2) % 2\r\n return phase === 0 ? text : null\r\n }\r\n\r\n async tick(time: number): Promise<{\r\n video?: VideoFrame | ImageBitmap | null\r\n audio?: Float32Array[]\r\n state: 'done' | 'success'\r\n }> {\r\n if (this.destroyed) return { state: 'done' }\r\n if (time >= this.opts.durationUs) return { state: 'done' }\r\n\r\n const visible = this.visibleTextAt(time)\r\n if (visible == null || visible.length === 0) {\r\n // 空文本时返回一张透明的同尺寸位图,避免 sprite 保留上一帧\r\n const bitmap = await this.renderEmpty()\r\n return { video: bitmap, state: 'success' }\r\n }\r\n\r\n const key = `${visible}|${JSON.stringify(this.opts.style)}`\r\n if (key !== this.cacheKey || this.cachedBitmap == null) {\r\n this.cachedBitmap?.close()\r\n this.cachedBitmap = await this.renderTextToFrame(visible)\r\n this.cacheKey = key\r\n }\r\n // ⚠️ 必须返回副本:Sprite 持有上一帧时会 close 掉它(实测 OffscreenSprite/VisibleSprite\r\n // 都会 `prev?.close()`),直接返回缓存位图会被关闭,下一帧读像素直接抛错。\r\n return { video: await createImageBitmap(this.cachedBitmap), state: 'success' }\r\n }\r\n\r\n /**\r\n * 把文字渲染成「整帧」位图(工程分辨率)。\r\n *\r\n * 为什么不能直接把 renderTxt2ImgBitmap 的结果当帧用:\r\n * 它返回的是文字自身尺寸的小图(几十到几百像素宽),Sprite 会把它拉伸到 rect 的\r\n * 1280×720 —— 文字被放大后严重模糊,且居中位置由 Sprite 决定而非文字块决定。\r\n * 因此这里先渲染文字小图,再按比例居中合成到工程尺寸的画布上。\r\n */\r\n private async renderTextToFrame(text: string): Promise<ImageBitmap> {\r\n const { width, height } = this.opts\r\n const canvas = new OffscreenCanvas(width, height)\r\n const ctx = canvas.getContext('2d')\r\n if (ctx == null) throw new Error('无法创建文字渲染上下文')\r\n\r\n const layer = await renderTxt2ImgBitmap(text, cssFromStyle(this.opts.style))\r\n const align = this.opts.style.align\r\n const margin = Math.round(width * 0.05)\r\n // 文字超宽时等比缩小,保证完整可见\r\n const maxWidth = width - margin * 2\r\n const scale = layer.width > maxWidth ? maxWidth / layer.width : 1\r\n const drawW = Math.round(layer.width * scale)\r\n const drawH = Math.round(layer.height * scale)\r\n const x =\r\n align === 'left' ? margin : align === 'right' ? width - margin - drawW : (width - drawW) / 2\r\n const y = Math.round((height - drawH) / 2)\r\n\r\n ctx.drawImage(layer, x, y, drawW, drawH)\r\n layer.close()\r\n return canvas.transferToImageBitmap()\r\n }\r\n\r\n private emptyBitmap: ImageBitmap | null = null\r\n\r\n private async renderEmpty(): Promise<ImageBitmap> {\r\n if (this.emptyBitmap != null) return this.emptyBitmap\r\n const canvas = new OffscreenCanvas(this.opts.width, this.opts.height)\r\n this.emptyBitmap = canvas.transferToImageBitmap()\r\n return this.emptyBitmap\r\n }\r\n\r\n async split(time: number): Promise<[this, this]> {\r\n if (time <= 0 || time >= this.opts.durationUs) {\r\n throw new Error('\"time\" out of bounds')\r\n }\r\n const left = new TextClip({ ...this.opts, durationUs: time })\r\n const right = new TextClip({\r\n ...this.opts,\r\n durationUs: this.opts.durationUs - time,\r\n // 右半段继承动画相位,避免打字机动画从 0 重新开始\r\n animOffsetUs: this.opts.animOffsetUs + time,\r\n })\r\n await Promise.all([left.ready, right.ready])\r\n return [left as this, right as this]\r\n }\r\n\r\n async clone(): Promise<this> {\r\n const copy = new TextClip({ ...this.opts })\r\n await copy.ready\r\n return copy as this\r\n }\r\n\r\n destroy(): void {\r\n this.destroyed = true\r\n this.cachedBitmap?.close()\r\n this.cachedBitmap = null\r\n this.emptyBitmap?.close()\r\n this.emptyBitmap = null\r\n }\r\n}\r\n\r\nexport { cssFromStyle }\r\n","/**\r\n * File → IClip 的分派工厂。\r\n *\r\n * ⚠️ 本文件是 `@webav/*` 的合法引用点之一(另一个是 core/webav 下的其他模块)。\r\n * 不要在 features / store / components 中直接 import WebAV。\r\n *\r\n * 所有 clip 实例的生命周期由 `ClipRegistry` 统一管理,这里只负责创建。\r\n */\r\n\r\nimport {\r\n AudioClip,\r\n EmbedSubtitlesClip,\r\n ImgClip,\r\n MP4Clip,\r\n} from '@webav/av-cliper'\r\nimport type { IClip } from '@webav/av-cliper'\r\nimport type { MediaAsset, SubtitleStyle, TextStyle } from '../../types'\r\nimport type { MP4ClipMeta } from './meta'\r\nimport { bufferToStream, fileToStream } from '../../media/file-reader'\r\nimport { DEFAULT_STILL_DURATION_US } from '../../constants'\r\nimport { TextClip } from './text-clip'\r\nimport { DEFAULT_SUBTITLE_STYLE, DEFAULT_TEXT_STYLE } from './styles'\r\n\r\nexport interface AssetMetaSnapshot {\r\n durationUs: number\r\n width: number\r\n height: number\r\n hasAudio: boolean\r\n}\r\n\r\n/**\r\n * 创建素材的「母版」clip 并读取 meta。\r\n *\r\n * 对静态图/文字/字幕,`meta.duration` 可能是 Infinity,这里会归一成有限值:\r\n * 真正的时长由时间轴 clip 决定,母版只用于 clone 与抽帧。\r\n */\r\nexport async function createClipFromFile(\r\n file: File,\r\n kind: MediaAsset['kind'],\r\n): Promise<{ clip: IClip; meta: AssetMetaSnapshot }> {\r\n switch (kind) {\r\n case 'video': {\r\n const clip = new MP4Clip(fileToStream(file))\r\n const meta = (await clip.ready) as MP4ClipMeta\r\n return {\r\n clip,\r\n meta: {\r\n durationUs: meta.duration,\r\n width: meta.width,\r\n height: meta.height,\r\n hasAudio: meta.audioChanCount > 0,\r\n },\r\n }\r\n }\r\n case 'audio': {\r\n const clip = new AudioClip(fileToStream(file))\r\n const meta = await clip.ready\r\n return {\r\n clip,\r\n meta: {\r\n durationUs: meta.duration,\r\n width: 0,\r\n height: 0,\r\n hasAudio: true,\r\n },\r\n }\r\n }\r\n case 'image': {\r\n const clip = new ImgClip(fileToStream(file))\r\n const meta = await clip.ready\r\n return {\r\n clip,\r\n meta: {\r\n durationUs: Number.isFinite(meta.duration) ? meta.duration : DEFAULT_STILL_DURATION_US,\r\n width: meta.width,\r\n height: meta.height,\r\n hasAudio: false,\r\n },\r\n }\r\n }\r\n case 'subtitle': {\r\n const text = await file.text()\r\n const clip = new EmbedSubtitlesClip(text, {\r\n videoWidth: 1280,\r\n videoHeight: 720,\r\n })\r\n const meta = await clip.ready\r\n return {\r\n clip,\r\n meta: {\r\n durationUs: Number.isFinite(meta.duration) ? meta.duration : DEFAULT_STILL_DURATION_US,\r\n width: meta.width,\r\n height: meta.height,\r\n hasAudio: false,\r\n },\r\n }\r\n }\r\n default:\r\n throw new Error(`Unsupported asset kind: ${kind}`)\r\n }\r\n}\r\n\r\nexport interface TextClipInit {\r\n text: string\r\n style: TextStyle\r\n width: number\r\n height: number\r\n durationUs: number\r\n}\r\n\r\n/** 文字素材:不走文件,直接由 TextClip 生成 */\r\nexport function createTextClip(init: TextClipInit): IClip {\r\n return new TextClip({\r\n text: init.text,\r\n style: init.style,\r\n width: init.width,\r\n height: init.height,\r\n durationUs: init.durationUs,\r\n })\r\n}\r\n\r\nexport interface SubtitleClipInit {\r\n srt: string\r\n style: SubtitleStyle\r\n width: number\r\n height: number\r\n}\r\n\r\nexport function createSubtitleClip(init: SubtitleClipInit): IClip {\r\n return new EmbedSubtitlesClip(init.srt, {\r\n videoWidth: init.width,\r\n videoHeight: init.height,\r\n color: init.style.color,\r\n textBgColor: init.style.textBgColor,\r\n fontFamily: init.style.fontFamily,\r\n fontSize: init.style.fontSize,\r\n strokeStyle: init.style.strokeStyle,\r\n lineWidth: init.style.lineWidth,\r\n bottomOffset: init.style.bottomOffset,\r\n })\r\n}\r\n\r\n/** Worker 内重建 clip:只支持可序列化的素材类型 */\r\nexport function createClipFromBuffer(\r\n buffer: ArrayBuffer,\r\n kind: MediaAsset['kind'],\r\n opts?: {\r\n width?: number\r\n height?: number\r\n text?: string\r\n textStyle?: TextStyle\r\n srt?: string\r\n subtitleStyle?: SubtitleStyle\r\n durationUs?: number\r\n },\r\n): IClip {\r\n switch (kind) {\r\n case 'video':\r\n return new MP4Clip(bufferToStream(buffer))\r\n case 'audio':\r\n return new AudioClip(bufferToStream(buffer))\r\n case 'image':\r\n return new ImgClip(bufferToStream(buffer))\r\n case 'subtitle':\r\n return createSubtitleClip({\r\n srt: opts?.srt ?? '',\r\n style: opts?.subtitleStyle ?? DEFAULT_SUBTITLE_STYLE,\r\n width: opts?.width ?? 1280,\r\n height: opts?.height ?? 720,\r\n })\r\n case 'text':\r\n return createTextClip({\r\n text: opts?.text ?? '',\r\n style: opts?.textStyle ?? DEFAULT_TEXT_STYLE,\r\n width: opts?.width ?? 1280,\r\n height: opts?.height ?? 720,\r\n durationUs: opts?.durationUs ?? DEFAULT_STILL_DURATION_US,\r\n })\r\n default:\r\n throw new Error(`Worker 内不支持重建素材类型:${kind}`)\r\n }\r\n}\r\n","/**\r\n * 自定义 IClip:给素材加「入点」偏移与时长上限。\r\n *\r\n * 为什么需要:WebAV 的 Sprite 没有 in-point 概念 —— `sprite.tick(time - offset)`\r\n * 永远从 0 开始取数据。时间轴上裁剪、分割后的 clip 需要从源素材的中间某处开始播放,\r\n * 因此用这层包装把时间平移 `inPointUs`。\r\n *\r\n * 单位:全部微秒。\r\n */\r\n\r\nimport type { IClip } from '@webav/av-cliper'\r\nimport type { ClipMetaBase as IClipMeta } from './meta'\r\n\r\nexport interface OffsetClipOptions {\r\n /** 源素材入点(微秒) */\r\n inPointUs: number\r\n /** 该 clip 在时间轴上的可见时长(微秒)。超过后 tick 返回 done */\r\n durationUs: number\r\n}\r\n\r\nexport class OffsetClip implements IClip {\r\n readonly ready: Promise<IClipMeta>\r\n private inner: IClip\r\n private opts: OffsetClipOptions\r\n private metaValue: IClipMeta\r\n private destroyed = false\r\n\r\n constructor(inner: IClip, opts: OffsetClipOptions) {\r\n this.inner = inner\r\n this.opts = opts\r\n this.metaValue = {\r\n width: 0,\r\n height: 0,\r\n duration: opts.durationUs,\r\n }\r\n this.ready = inner.ready.then(async (meta) => {\r\n /*\r\n * 预热:把内部 clip 的游标定位到入点。\r\n *\r\n * 为什么必须在这里做,而不是等第一次 tick:\r\n * `AudioClip.tick` 返回的是「距上次 tick 的增量」。如果不预热,第一次\r\n * tick 的时间就是 `inPointUs`,于是内部一次性吐出**从 0 到入点**的全部\r\n * 音频(例如 1 秒 = 48000 帧)。Combinator 的混音器按 1024 帧分片、\r\n * 中间缓冲只有 3 片,超出的部分会 `subarray` 出空数组,最终\r\n * `new AudioData({ data: 空 })` 抛 \"data is too small ... received 0\"。\r\n *\r\n * 预热后 `lastUs` 已被推到入点,后续每次 tick 只产生一帧的增量,\r\n * 与「没有裁剪的 clip」完全一致。画面侧同样受益:少解码一帧旧画面。\r\n *\r\n * 预热返回的数据直接丢弃:那是入点之前的内容,本来就不该出现在时间轴上。\r\n */\r\n try {\r\n await inner.tick(opts.inPointUs)\r\n } catch {\r\n // 预热失败不应阻断 clip 可用:最坏情况退回首次 tick 的旧行为\r\n }\r\n this.metaValue = {\r\n width: meta.width,\r\n height: meta.height,\r\n duration: opts.durationUs,\r\n }\r\n return this.metaValue\r\n })\r\n }\r\n\r\n get meta(): IClipMeta {\r\n return this.metaValue\r\n }\r\n\r\n async tick(time: number): Promise<{\r\n video?: VideoFrame | ImageBitmap | null\r\n audio?: Float32Array[]\r\n state: 'done' | 'success'\r\n }> {\r\n if (this.destroyed) return { state: 'done' }\r\n if (time >= this.opts.durationUs) return { audio: [], state: 'done' }\r\n return this.inner.tick(time + this.opts.inPointUs)\r\n }\r\n\r\n async split(time: number): Promise<[this, this]> {\r\n if (time <= 0 || time >= this.opts.durationUs) {\r\n throw new Error('\"time\" out of bounds')\r\n }\r\n const leftInner = await this.inner.clone()\r\n const rightInner = await this.inner.clone()\r\n const left = new OffsetClip(leftInner, {\r\n inPointUs: this.opts.inPointUs,\r\n durationUs: time,\r\n })\r\n const right = new OffsetClip(rightInner, {\r\n inPointUs: this.opts.inPointUs + time,\r\n durationUs: this.opts.durationUs - time,\r\n })\r\n await Promise.all([left.ready, right.ready])\r\n return [left as this, right as this]\r\n }\r\n\r\n async clone(): Promise<this> {\r\n const copy = new OffsetClip(await this.inner.clone(), { ...this.opts })\r\n await copy.ready\r\n return copy as this\r\n }\r\n\r\n destroy(): void {\r\n if (this.destroyed) return\r\n this.destroyed = true\r\n this.inner.destroy()\r\n }\r\n}\r\n","/**\r\n * IClip 运行时注册表。\r\n *\r\n * 职责:\r\n * - 持有每个素材的「母版」IClip 与源 File,用于 clone / 抽帧 / 重建;\r\n * - 为时间轴上的每个 clip 派生独立 IClip 实例(通过 clone 或重新构造);\r\n * - 统一 destroy,避免内存泄漏 —— 大素材下这是最常见的崩溃原因。\r\n *\r\n * 派生实例会被 `OffsetClip` 包裹,从而支持时间轴上的入点(in-point)裁剪。\r\n *\r\n * 本模块不依赖 React;调用方(store / hooks)负责触发。\r\n */\r\n\r\nimport type { IClip } from '@webav/av-cliper'\r\nimport type { MediaAsset } from '../../types'\r\nimport { createClipFromFile, createSubtitleClip, createTextClip } from './clip-factory'\r\nimport { DEFAULT_SUBTITLE_STYLE, DEFAULT_TEXT_STYLE } from './styles'\r\nimport { OffsetClip } from './offset-clip'\r\nimport { releaseThumbnails } from '../../media/thumbnails'\r\n\r\ninterface AssetRuntime {\r\n asset: MediaAsset\r\n file: File | null\r\n /** 母版 clip:文件素材才有 */\r\n master: IClip | null\r\n}\r\n\r\nexport interface CreateInstanceParams {\r\n timelineClipId: string\r\n assetId: string\r\n inPointUs: number\r\n durationUs: number\r\n text?: string\r\n textStyle?: MediaAsset['textStyle']\r\n srt?: string\r\n subtitleStyle?: MediaAsset['subtitleStyle']\r\n projectWidth: number\r\n projectHeight: number\r\n}\r\n\r\nexport class ClipRegistry {\r\n private assets = new Map<string, AssetRuntime>()\r\n private derived = new Map<string, IClip>()\r\n\r\n registerAsset(asset: MediaAsset, file: File | null, master: IClip | null): void {\r\n this.assets.set(asset.id, { asset, file, master })\r\n }\r\n\r\n getAsset(assetId: string): MediaAsset | undefined {\r\n return this.assets.get(assetId)?.asset\r\n }\r\n\r\n getFile(assetId: string): File | null {\r\n return this.assets.get(assetId)?.file ?? null\r\n }\r\n\r\n getMaster(assetId: string): IClip | null {\r\n return this.assets.get(assetId)?.master ?? null\r\n }\r\n\r\n listAssets(): MediaAsset[] {\r\n return Array.from(this.assets.values()).map((r) => r.asset)\r\n }\r\n\r\n /**\r\n * 为非文件素材(文字 / 字幕)重建实例。\r\n * 这两个类型无法 clone(clone 会带走旧的 duration / 内容),必须每次新建。\r\n */\r\n private buildProceduralClip(params: CreateInstanceParams, asset: MediaAsset): IClip {\r\n if (asset.kind === 'text') {\r\n return createTextClip({\r\n text: params.text ?? asset.text ?? '文本',\r\n style: { ...DEFAULT_TEXT_STYLE, ...(asset.textStyle ?? {}), ...(params.textStyle ?? {}) },\r\n width: params.projectWidth,\r\n height: params.projectHeight,\r\n durationUs: params.durationUs,\r\n })\r\n }\r\n return createSubtitleClip({\r\n srt: params.srt ?? asset.srt ?? '',\r\n style: { ...DEFAULT_SUBTITLE_STYLE, ...(asset.subtitleStyle ?? {}), ...(params.subtitleStyle ?? {}) },\r\n width: params.projectWidth,\r\n height: params.projectHeight,\r\n })\r\n }\r\n\r\n /**\r\n * 为时间轴 clip 创建独立 IClip(会被 OffsetClip 包裹以支持入点)。\r\n *\r\n * @throws 素材已不存在或缺少数据源时抛出,调用方应提示用户重新导入\r\n */\r\n async createInstance(params: CreateInstanceParams): Promise<IClip> {\r\n const runtime = this.assets.get(params.assetId)\r\n if (!runtime) throw new Error(`素材已不存在:${params.assetId}`)\r\n\r\n this.destroyInstance(params.timelineClipId)\r\n\r\n const { asset } = runtime\r\n let clip: IClip\r\n\r\n if (asset.kind === 'text' || asset.kind === 'subtitle') {\r\n clip = this.buildProceduralClip(params, asset)\r\n } else if (runtime.master) {\r\n clip = await runtime.master.clone()\r\n } else if (runtime.file) {\r\n // 兜底:母版失效时从文件重建\r\n const created = await createClipFromFile(runtime.file, asset.kind)\r\n clip = created.clip\r\n } else {\r\n throw new Error(`素材缺少可用的数据源:${asset.name}`)\r\n }\r\n\r\n const wrapped =\r\n params.inPointUs > 0\r\n ? new OffsetClip(clip, {\r\n inPointUs: params.inPointUs,\r\n durationUs: params.durationUs,\r\n })\r\n : clip\r\n\r\n this.derived.set(params.timelineClipId, wrapped)\r\n return wrapped\r\n }\r\n\r\n getInstance(timelineClipId: string): IClip | null {\r\n return this.derived.get(timelineClipId) ?? null\r\n }\r\n\r\n destroyInstance(timelineClipId: string): void {\r\n const clip = this.derived.get(timelineClipId)\r\n if (!clip) return\r\n try {\r\n clip.destroy()\r\n } catch {\r\n // destroy 失败不应阻断清理流程\r\n }\r\n this.derived.delete(timelineClipId)\r\n }\r\n\r\n /**\r\n * 重新导入同名文件后,把二进制与母版换回原 assetId(id 不变,时间轴立即恢复)。\r\n *\r\n * 注意:旧母版与新文件可能不是同一内容,但工程里记录的 meta 已被新素材覆盖,\r\n * 因此这里直接替换而不是合并。\r\n */\r\n replaceAssetSource(assetId: string, file: File, master: IClip | null): void {\r\n const existing = this.assets.get(assetId)\r\n if (existing == null) return\r\n try {\r\n existing.master?.destroy()\r\n } catch {\r\n // ignore\r\n }\r\n this.assets.set(assetId, { ...existing, file, master })\r\n // 派生实例基于旧母版,全部销毁让上层按新素材重建\r\n this.destroyAllInstances()\r\n }\r\n\r\n /** 移除素材:销毁母版与缩略图 URL(派生实例由时间轴侧负责) */\r\n removeAsset(assetId: string): void {\r\n const runtime = this.assets.get(assetId)\r\n try {\r\n runtime?.master?.destroy()\r\n } catch {\r\n // ignore\r\n }\r\n this.assets.delete(assetId)\r\n releaseThumbnails(assetId)\r\n }\r\n\r\n destroyAllInstances(): void {\r\n for (const [id] of this.derived) {\r\n this.destroyInstance(id)\r\n }\r\n }\r\n\r\n /** 整体销毁(应用卸载) */\r\n dispose(): void {\r\n this.destroyAllInstances()\r\n for (const [assetId, runtime] of this.assets) {\r\n try {\r\n runtime.master?.destroy()\r\n } catch {\r\n // ignore\r\n }\r\n releaseThumbnails(assetId)\r\n }\r\n this.assets.clear()\r\n }\r\n\r\n /** 调试用:当前资源计数 */\r\n stats(): { assets: number; instances: number } {\r\n return { assets: this.assets.size, instances: this.derived.size }\r\n }\r\n}\r\n","/**\r\n * 主播放时钟(框架无关)。\r\n *\r\n * 为什么不用 setInterval 累加:定时器精度差且会累积漂移。\r\n * 为什么不用 AVCanvas.play 当主时钟:它按固定 33ms 步进,且无法与外部音频调度对齐。\r\n *\r\n * 这里以 `AudioContext.currentTime`(硬件音频时钟)为唯一时间源:\r\n *\r\n * currentUs = anchorUs + (ctx.currentTime - anchorCtxTime) * 1e6 * rate\r\n *\r\n * 暂停/跳转/变速时重设锚点,因此不存在累积误差。\r\n */\r\n\r\nexport type PlaybackState = 'paused' | 'playing'\r\n\r\nexport interface ClockAnchor {\r\n /** AudioContext 时间(秒) */\r\n ctxTime: number\r\n /** 对应的工程时间(微秒) */\r\n us: number\r\n rate: number\r\n}\r\n\r\ntype Listener = () => void\r\n\r\nexport class PlaybackClock {\r\n private ctx: AudioContext | null = null\r\n private anchorCtxTime = 0\r\n private anchorUs = 0\r\n private rate = 1\r\n private state: PlaybackState = 'paused'\r\n private listeners = new Set<Listener>()\r\n\r\n /** 惰性创建 AudioContext;必须由用户手势触发 resume,否则浏览器会挂起 */\r\n ensureContext(): AudioContext {\r\n if (this.ctx == null) {\r\n this.ctx = new AudioContext({ latencyHint: 'interactive' })\r\n }\r\n if (this.ctx.state === 'suspended') {\r\n void this.ctx.resume()\r\n }\r\n return this.ctx\r\n }\r\n\r\n get context(): AudioContext | null {\r\n return this.ctx\r\n }\r\n\r\n get isPlaying(): boolean {\r\n return this.state === 'playing'\r\n }\r\n\r\n get playbackRate(): number {\r\n return this.rate\r\n }\r\n\r\n /** 当前工程时间(微秒) */\r\n get currentUs(): number {\r\n if (this.state !== 'playing' || this.ctx == null) return this.anchorUs\r\n const elapsed = this.ctx.currentTime - this.anchorCtxTime\r\n // clamp:标签页切后台后 ctx.currentTime 仍会前进,但不应让播放头越过终点\r\n return Math.max(0, Math.round(this.anchorUs + elapsed * 1_000_000 * this.rate))\r\n }\r\n\r\n /** 当前锚点,音频调度器据此换算 AudioContext 时间 */\r\n get anchor(): ClockAnchor {\r\n return { ctxTime: this.anchorCtxTime, us: this.anchorUs, rate: this.rate }\r\n }\r\n\r\n /** 播放头位置(暂停时也有效) */\r\n get positionUs(): number {\r\n return this.currentUs\r\n }\r\n\r\n play(fromUs: number, rate = this.rate): void {\r\n this.ensureContext()\r\n this.rate = rate > 0 ? rate : 1\r\n this.anchorUs = Math.max(0, fromUs)\r\n this.anchorCtxTime = this.ctx?.currentTime ?? 0\r\n this.state = 'playing'\r\n this.emit()\r\n }\r\n\r\n pause(): void {\r\n if (this.state === 'paused') return\r\n this.anchorUs = this.currentUs\r\n this.state = 'paused'\r\n this.emit()\r\n }\r\n\r\n /** 跳转;播放中跳转会重设锚点并通知订阅者(需要重启音频调度) */\r\n seek(us: number): void {\r\n const next = Math.max(0, Math.round(us))\r\n if (this.state === 'playing' && this.ctx != null) {\r\n this.anchorUs = next\r\n this.anchorCtxTime = this.ctx.currentTime\r\n } else {\r\n this.anchorUs = next\r\n }\r\n this.emit()\r\n }\r\n\r\n setRate(rate: number): void {\r\n const next = rate > 0 ? rate : 1\r\n if (this.state === 'playing' && this.ctx != null) {\r\n // 以当前位置为新锚点,避免速率切换造成时间跳变\r\n this.anchorUs = this.currentUs\r\n this.anchorCtxTime = this.ctx.currentTime\r\n }\r\n this.rate = next\r\n this.emit()\r\n }\r\n\r\n subscribe(listener: Listener): () => void {\r\n this.listeners.add(listener)\r\n return () => this.listeners.delete(listener)\r\n }\r\n\r\n private emit(): void {\r\n for (const l of this.listeners) l()\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n this.listeners.clear()\r\n try {\r\n await this.ctx?.close()\r\n } catch {\r\n // 关闭失败无需处理\r\n }\r\n this.ctx = null\r\n }\r\n}\r\n","/**\r\n * 预览音频引擎。\r\n *\r\n * 为什么需要它:预览画面由 AVCanvas.previewFrame 逐帧驱动(受主时钟控制),\r\n * AVCanvas 自带的播放器会再建一套 33ms 步进的音频调度,两套时钟必然打架。\r\n * 因此这里把音频调度完全接管:按 clip 的 offset/duration 把 PCM 切片排进\r\n * AudioContext 的时间轴,与 PlaybackClock 使用同一个 AudioContext,天然同步。\r\n *\r\n * PCM 来源:对含音轨的素材用 AudioClip 解码一次(内部走 decodeAudioData),\r\n * 之后按 assetId 缓存,避免重复解码。\r\n *\r\n * 音量与淡入淡出通过 GainNode 包络实现;变速使用 AudioBufferSourceNode.playbackRate,\r\n * 与 WebAV 一致地「变调」。\r\n */\r\n\r\nimport type { MediaAsset, TimelineClip, Track } from '../../types'\r\n\r\n/** 预览音频引擎需要的最小 clip 接口(避免 core/clock 直接依赖 @webav) */\r\nexport interface PcmSource {\r\n ready: Promise<{ duration: number; sampleRate?: number; chanCount?: number; width: number; height: number }>\r\n getPCMData(): Float32Array[]\r\n destroy(): void\r\n}\r\n\r\ninterface PcmEntry {\r\n channels: Float32Array[]\r\n sampleRate: number\r\n durationUs: number\r\n /** 原始解码结果的引用,卸载时 destroy */\r\n clip: PcmSource | null\r\n}\r\n\r\ninterface ScheduledSource {\r\n source: AudioBufferSourceNode\r\n gain: GainNode\r\n}\r\n\r\nexport interface AudioEngineClip {\r\n clip: TimelineClip\r\n track: Track\r\n assetKind: MediaAsset['kind']\r\n}\r\n\r\nconst PCM_CACHE_LIMIT = 6\r\n\r\nexport class PreviewAudioEngine {\r\n private ctx: AudioContext | null = null\r\n private pcmCache = new Map<string, Promise<PcmEntry>>()\r\n private scheduled: ScheduledSource[] = []\r\n private masterGain: GainNode | null = null\r\n private masterVolume = 1\r\n\r\n attach(ctx: AudioContext): void {\r\n if (this.ctx === ctx) return\r\n this.stopAll()\r\n this.ctx = ctx\r\n this.masterGain = ctx.createGain()\r\n this.masterGain.gain.value = this.masterVolume\r\n this.masterGain.connect(ctx.destination)\r\n }\r\n\r\n setMasterVolume(volume: number): void {\r\n this.masterVolume = Math.max(0, Math.min(1, volume))\r\n if (this.masterGain) this.masterGain.gain.value = this.masterVolume\r\n }\r\n\r\n /**\r\n * 注册一个素材的 PCM 解码器。\r\n *\r\n * @param load 返回该素材对应的 AudioClip(视频素材也可,MP4 的 AAC 音轨可被 decodeAudioData 解析)\r\n */\r\n registerAsset(assetId: string, load: () => Promise<PcmSource>): void {\r\n if (this.pcmCache.has(assetId)) return\r\n const promise = (async (): Promise<PcmEntry> => {\r\n const clip = await load()\r\n const meta = await clip.ready\r\n return {\r\n channels: clip.getPCMData(),\r\n sampleRate: (meta as { sampleRate?: number }).sampleRate ?? 48000,\r\n durationUs: meta.duration,\r\n clip,\r\n }\r\n })().catch((err: unknown) => {\r\n // 解码失败时移除缓存项,避免后续每次播放都命中一个被 reject 的 Promise。\r\n // 预览音频失败不影响画面播放,因此只记录警告。\r\n console.warn('[preview-audio] 音频解码失败', assetId, err)\r\n this.pcmCache.delete(assetId)\r\n throw err\r\n })\r\n // 该 Promise 主要供 scheduleFrom 内部 await;没有播放请求时无人 await,\r\n // 必须自己标记为「已处理」,否则会变成 unhandled rejection。\r\n promise.catch(() => undefined)\r\n this.pcmCache.set(assetId, promise)\r\n // 简单 LRU:超过上限时淘汰最早的\r\n if (this.pcmCache.size > PCM_CACHE_LIMIT) {\r\n const firstKey = this.pcmCache.keys().next().value\r\n if (firstKey) {\r\n const entry = this.pcmCache.get(firstKey)\r\n this.pcmCache.delete(firstKey)\r\n void entry?.then((e) => e.clip?.destroy()).catch(() => undefined)\r\n }\r\n }\r\n }\r\n\r\n hasAsset(assetId: string): boolean {\r\n return this.pcmCache.has(assetId)\r\n }\r\n\r\n /**\r\n * 从 `fromUs` 开始排程所有可听 clip 的音频。\r\n *\r\n * @param fromUs 播放起点(微秒)\r\n * @param clips 当前时间轴上的全部 clip\r\n * @returns 已排程的结束时间(微秒),用于 clock 自动暂停\r\n */\r\n async scheduleFrom(\r\n fromUs: number,\r\n clips: AudioEngineClip[],\r\n projectEndUs: number,\r\n ): Promise<void> {\r\n this.stopAll()\r\n const ctx = this.ctx\r\n const master = this.masterGain\r\n if (ctx == null || master == null) return\r\n\r\n const audible = clips.filter(\r\n (c) => c.track.kind === 'audio' || c.assetKind === 'video' || c.assetKind === 'audio',\r\n )\r\n if (audible.length === 0) return\r\n\r\n const ctxNow = ctx.currentTime\r\n const startUs = Math.max(0, fromUs)\r\n\r\n for (const item of audible) {\r\n const { clip, track } = item\r\n if (track.muted || track.hidden) continue\r\n if (clip.muted) continue\r\n const clipEnd = clip.startUs + clip.durationUs\r\n if (clipEnd <= startUs) continue\r\n\r\n const entry = await this.getEntry(clip.assetId)\r\n if (entry == null || entry.channels.length === 0) continue\r\n\r\n const playStartUs = Math.max(startUs, clip.startUs)\r\n const fadeOffsetUs = playStartUs - clip.startUs\r\n const sourceStartUs = clip.inPointUs + fadeOffsetUs * clip.playbackRate\r\n const remainingTimelineUs = clipEnd - playStartUs\r\n if (remainingTimelineUs <= 0) continue\r\n\r\n // 源素材可提供的剩余长度(防止越界)\r\n const sourceAvailableUs = Math.max(0, entry.durationUs - sourceStartUs)\r\n const playableTimelineUs = Math.min(\r\n remainingTimelineUs,\r\n sourceAvailableUs / Math.max(0.01, clip.playbackRate),\r\n )\r\n if (playableTimelineUs <= 0) continue\r\n\r\n const startSec = ctxNow + (playStartUs - startUs) / 1_000_000\r\n const buffer = this.sliceBuffer(ctx, entry, sourceStartUs, playableTimelineUs, clip.playbackRate)\r\n if (buffer == null) continue\r\n\r\n const gain = ctx.createGain()\r\n gain.connect(master)\r\n this.applyEnvelope(\r\n gain,\r\n startSec,\r\n playableTimelineUs / 1_000_000,\r\n clip,\r\n fadeOffsetUs,\r\n )\r\n\r\n const source = ctx.createBufferSource()\r\n source.buffer = buffer\r\n source.playbackRate.value = clip.playbackRate\r\n source.connect(gain)\r\n source.start(startSec)\r\n source.onended = () => {\r\n try {\r\n source.disconnect()\r\n gain.disconnect()\r\n } catch {\r\n // ignore\r\n }\r\n this.scheduled = this.scheduled.filter((s) => s.source !== source)\r\n }\r\n this.scheduled.push({ source, gain })\r\n }\r\n void projectEndUs\r\n }\r\n\r\n /** 立即停止所有已排程音频(暂停 / 跳转 / 释放时调用) */\r\n stopAll(): void {\r\n for (const { source, gain } of this.scheduled) {\r\n try {\r\n source.stop()\r\n } catch {\r\n // 未启动或已结束\r\n }\r\n try {\r\n source.disconnect()\r\n gain.disconnect()\r\n } catch {\r\n // ignore\r\n }\r\n }\r\n this.scheduled = []\r\n }\r\n\r\n private applyEnvelope(\r\n gain: GainNode,\r\n startSec: number,\r\n durationSec: number,\r\n clip: TimelineClip,\r\n fadeOffsetUs: number,\r\n ): void {\r\n const base = Math.max(0, clip.volume)\r\n const fadeInSec = clip.fadeInUs / 1_000_000\r\n const fadeOutSec = clip.fadeOutUs / 1_000_000\r\n // 已经越过淡入区时不应再从 0 起(跳转到中段播放)\r\n const passedFadeInUs = fadeOffsetUs\r\n if (fadeInSec > 0 && passedFadeInUs < clip.fadeInUs) {\r\n const remainingIn = (clip.fadeInUs - passedFadeInUs) / 1_000_000\r\n const startRatio = Math.max(0, 1 - passedFadeInUs / clip.fadeInUs)\r\n gain.gain.setValueAtTime(base * startRatio, startSec)\r\n gain.gain.linearRampToValueAtTime(base, startSec + Math.min(remainingIn, durationSec))\r\n } else {\r\n gain.gain.setValueAtTime(base, startSec)\r\n }\r\n if (fadeOutSec > 0) {\r\n const fadeStart = startSec + Math.max(0, durationSec - fadeOutSec)\r\n gain.gain.setValueAtTime(base, fadeStart)\r\n gain.gain.linearRampToValueAtTime(0, startSec + durationSec)\r\n }\r\n }\r\n\r\n private sliceBuffer(\r\n ctx: AudioContext,\r\n entry: PcmEntry,\r\n sourceStartUs: number,\r\n timelineDurationUs: number,\r\n rate: number,\r\n ): AudioBuffer | null {\r\n const sampleRate = entry.sampleRate\r\n const from = Math.floor((sourceStartUs / 1_000_000) * sampleRate)\r\n // 源素材实际消费的长度 = 时间轴时长 × 速率\r\n const sourceFrames = Math.floor((timelineDurationUs / 1_000_000) * sampleRate * rate)\r\n if (sourceFrames <= 0) return null\r\n\r\n const channelCount = Math.max(1, Math.min(2, entry.channels.length))\r\n const buffer = ctx.createBuffer(channelCount, sourceFrames, sampleRate)\r\n for (let ch = 0; ch < channelCount; ch += 1) {\r\n const src = entry.channels[ch] ?? entry.channels[0]\r\n if (!src) continue\r\n const out = buffer.getChannelData(ch)\r\n const limit = Math.min(sourceFrames, src.length - from)\r\n if (limit <= 0) continue\r\n out.set(src.subarray(from, from + limit), 0)\r\n }\r\n return buffer\r\n }\r\n\r\n private async getEntry(assetId: string): Promise<PcmEntry | null> {\r\n const promise = this.pcmCache.get(assetId)\r\n if (!promise) return null\r\n try {\r\n return await promise\r\n } catch {\r\n return null\r\n }\r\n }\r\n\r\n dispose(): void {\r\n this.stopAll()\r\n for (const promise of this.pcmCache.values()) {\r\n void promise.then((e) => e.clip?.destroy()).catch(() => undefined)\r\n }\r\n this.pcmCache.clear()\r\n try {\r\n this.masterGain?.disconnect()\r\n } catch {\r\n // ignore\r\n }\r\n this.masterGain = null\r\n this.ctx = null\r\n }\r\n\r\n /** 调试用:当前排程的音频源数量(验证预览音频是否真的在播放) */\r\n get scheduledCount(): number {\r\n return this.scheduled.length\r\n }\r\n\r\n /** 调试用:已缓存的素材 PCM 数量 */\r\n get cachedAssetCount(): number {\r\n return this.pcmCache.size\r\n }\r\n}\r\n","/**\r\n * 编辑器运行时(Engine Runtime)。\r\n *\r\n * 一个实例 = 一份完整的运行期资源:\r\n * - `ClipRegistry`:IClip 母版与派生实例\r\n * - `PlaybackClock`:基于 AudioContext 的主时钟\r\n * - `PreviewAudioEngine`:预览音频排程\r\n *\r\n * 之所以把它们收拢到一个对象:这三个组件共享同一个 `AudioContext`,生命周期必须\r\n * 严格一致。过去它们是模块级单例,导致同页多实例 / Electron 多窗口无法隔离,\r\n * 且切项目时无法真正释放(AudioContext 数量有上限,Chrome 大约 6 个)。\r\n *\r\n * 注意:本文件**不** import 任何 `@webav/*` 值,只依赖类型,因此可以安全地\r\n * 由 `createEditor()` 静态引入(真正的 WebAV 模块由 `engine/loader` 懒加载)。\r\n */\r\n\r\nimport type { IClip } from '@webav/av-cliper'\r\nimport type { MediaAsset } from '../types'\r\nimport { ClipRegistry } from '../engine/webav/clip-registry'\r\nimport { PlaybackClock } from '../engine/clock/playback-clock'\r\nimport { PreviewAudioEngine } from '../engine/clock/preview-audio'\r\n\r\nexport interface EditorRuntime {\r\n readonly clips: ClipRegistry\r\n readonly clock: PlaybackClock\r\n readonly audio: PreviewAudioEngine\r\n /** 释放全部资源;幂等 */\r\n dispose(): Promise<void>\r\n}\r\n\r\n/**\r\n * 创建运行时。\r\n *\r\n * 不在这里创建 AudioContext:浏览器的自动播放策略要求 AudioContext 必须由用户\r\n * 手势创建或 resume,因此延迟到第一次播放时由 `clock.ensureContext()` 触发。\r\n * 这样初始化(含 SSR 预渲染、Electron 窗口预热)不会产生被挂起的音频上下文。\r\n */\r\nexport function createEditorRuntime(): EditorRuntime {\r\n const clips = new ClipRegistry()\r\n const clock = new PlaybackClock()\r\n const audio = new PreviewAudioEngine()\r\n let disposed = false\r\n\r\n return {\r\n clips,\r\n clock,\r\n audio,\r\n\r\n async dispose() {\r\n if (disposed) return\r\n disposed = true\r\n // 顺序敏感:先停音频与播放(二者都依赖 AudioContext),再销毁 clip,\r\n // 最后关闭 AudioContext —— 反过来会让仍在排程的音频源持有已销毁的 PCM。\r\n audio.stopAll()\r\n clock.pause()\r\n audio.dispose()\r\n clips.dispose()\r\n await clock.dispose()\r\n },\r\n }\r\n}\r\n\r\n/** 素材在运行时中的绑定信息(供 UI 层判断能否播放) */\r\nexport interface AssetRuntimeInfo {\r\n asset: MediaAsset\r\n hasFile: boolean\r\n hasMaster: IClip | null\r\n}\r\n","/**\n * 异常 → 可展示文本。\n *\n * 各模块都会在 catch 里把 `unknown` 转成字符串,集中一处避免多份实现漂移。\n */\nexport function errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n","/**\r\n * 编辑器实例(Editor Handle)。\r\n *\r\n * 这是对外的统一门面:UI 包、接入层、宿主都只跟它打交道。它负责:\r\n * - 持有 store(工程 + 选择 + 播放头)与运行时(clips / clock / audio)\r\n * - 暴露命令式 API(play / seek / addFiles / export / undo …)\r\n * - 通过事件总线对外广播状态变化\r\n *\r\n * 设计原则:命令直接调 store / runtime,**不经过 React**。这样:\r\n * - `initCutWorkbench()` 这种非 React 接入方式可以完整操作编辑器;\r\n * - 事件总线成为唯一对外通知渠道,接入方不必知道内部 store 结构。\r\n *\r\n * 文件导入与导出会**懒加载**引擎的对应模块:宿主调 `addFiles()` / `export()`\r\n * 时不需要自己写 `await import('@cut-workbench/core/engine')`。\r\n */\r\n\r\nimport type { MediaAsset, Project, TimelineClip, Track } from '../types'\r\nimport type { SerializedProject } from '../project/schema'\r\nimport { deserializeProject, serializeProject } from '../project/schema'\r\nimport { createEditorStore } from './editor-store'\r\nimport { createHistoryStore } from './history-store'\r\nimport { createEditorRuntime, type EditorRuntime } from './runtime'\r\nimport { createEventBus, type EventBus } from './event-bus'\r\nimport { createEmptyProject, getProjectDuration } from '../timeline/model'\r\nimport { createMemoryStorage } from '../project/indexeddb-storage'\r\nimport type { StoragePort, SaveResult } from '../project/storage'\r\nimport type { HostBridge, SaveFileResult } from '../ports/host-bridge'\r\nimport { createWebBridge } from '../ports/web-bridge'\r\nimport { detectEnvironment, type CapabilityReport } from '../capability'\r\nimport { errorMessage } from '../lib/error'\r\nimport { ExportCancelledError } from '../errors'\r\nimport {\r\n createExportFilename,\r\n resolveExportSettings,\r\n type ExportHandle,\r\n type ExportRequest,\r\n type ExportResult,\r\n} from '../export/types'\r\nimport type {\r\n AddFilesOptions,\r\n AddFilesResult,\r\n AddSubtitleAssetParams,\r\n AddTextAssetParams,\r\n AutoSaveOptions,\r\n CutEditorHandle,\r\n CutEventMap,\r\n EditorCommands,\r\n EditorOptions,\r\n ImportJsonOptions,\r\n ImportJsonResult,\r\n SaveAsResult,\r\n} from './handle-types'\r\n\r\nexport type {\r\n AddFilesOptions,\r\n AddFilesResult,\r\n AddSubtitleAssetParams,\r\n AddTextAssetParams,\r\n AutoSaveOptions,\r\n CutEditorHandle,\r\n CutEventMap,\r\n EditorCommands,\r\n EditorOptions,\r\n ImportJsonOptions,\r\n ImportJsonResult,\r\n SaveAsResult,\r\n}\r\n\r\nexport type { EditorRuntime }\r\n\r\nfunction resolveBridge(bridge: HostBridge | undefined): HostBridge {\r\n if (bridge != null) return bridge\r\n // 只在浏览器里才会真正调用 files 相关方法,Node 环境下构造是安全的\r\n return createWebBridge()\r\n}\r\n\r\n// 引擎模块按需加载:WebAV 的 opfs-tools 在模块加载期访问 navigator.storage,\r\n// 静态引入会破坏「Node 里 import 本包」以及非 Chromium 的初始化路径。\r\nconst loadImportFiles = () => import('../engine/webav/import-files')\r\nconst loadImportPipeline = () => import('../engine/webav/import-pipeline')\r\nconst loadExportRunner = () => import('../engine/webav/export-runner')\r\n\r\n/**\r\n * 创建编辑器实例。\r\n *\r\n * 不加载 WebAV:引擎模块在首次导入素材 / 导出时才动态加载 —— opfs-tools\r\n * 在模块加载期就会访问 `navigator.storage`,非 Chromium 环境会直接抛错。\r\n */\r\nexport function createEditor(options: EditorOptions = {}): CutEditorHandle {\r\n const id = `editor_${Math.random().toString(36).slice(2, 10)}`\r\n const bridge = resolveBridge(options.bridge)\r\n\r\n // —— 持久化目标:可被 saveAs() 替换 ——\r\n let storage: StoragePort = options.storage ?? createMemoryStorage()\r\n let namespace = options.namespace ?? 'default'\r\n let autoSave: AutoSaveOptions | null = options.autoSave ?? null\r\n\r\n // projectId 只影响「空工程」;传了完整工程时以工程自身 id 为准\r\n const initialProject: Project =\r\n options.project ??\r\n (options.projectId != null && options.projectId !== ''\r\n ? { ...createEmptyProject(), id: options.projectId }\r\n : createEmptyProject())\r\n\r\n const bus: EventBus<CutEventMap> = createEventBus((err) => {\r\n bus.emit('error', { scope: 'event-listener', error: err })\r\n })\r\n const history = createHistoryStore()\r\n const store = createEditorStore({\r\n project: initialProject,\r\n assets: options.assets,\r\n history,\r\n })\r\n const runtime = createEditorRuntime()\r\n\r\n let disposed = false\r\n let lastPlaying = false\r\n let capabilityPromise: Promise<CapabilityReport> | null = null\r\n /** 防抖计时器:最后一次变更后等待 intervalMs 再写盘 */\r\n let autoSaveTimer: ReturnType<typeof setTimeout> | null = null\r\n /** 串行化写盘:自动保存与手动保存永不并发(并发会交错写同一 namespace) */\r\n let saveChain: Promise<SaveAsResult> = Promise.resolve({ ok: true })\r\n\r\n // —— 把 store 的变化转成对外事件 ——\r\n // 只做「引用比较 → 派发」,不做深比较:store 已经是不可变更新,引用变化即语义变化。\r\n let prevProject = store.getState().project\r\n let prevAssets = store.getState().assets\r\n let prevSelection = store.getState().selectedClipIds\r\n let prevAssetId = store.getState().selectedAssetId\r\n let prevPlayhead = store.getState().playheadUs\r\n let prevCanUndo = history.getState().canUndo()\r\n let prevDirty = store.getState().dirty\r\n\r\n /** 当前写盘的存储与命名空间(autoSave 指定了自己的目标时以它为准) */\r\n function saveTarget(): { port: StoragePort; ns: string } {\r\n if (autoSave != null) return { port: autoSave.storage, ns: autoSave.namespace }\r\n return { port: storage, ns: namespace }\r\n }\r\n\r\n /**\r\n * 自动保存调度(防抖)。\r\n *\r\n * 语义是「最后一次结构性变更后静置 intervalMs 才写盘」,而不是\r\n * 「第一次变更后固定 intervalMs 写盘」:\r\n * 后者在持续编辑(拖滑块、连续裁剪)时会中途写一次,既浪费 I/O,\r\n * 又会在宿主展示「保存中」时产生无意义的闪烁。每次新变更都重置计时器。\r\n */\r\n function scheduleAutoSave(): void {\r\n if (autoSave == null || disposed) return\r\n if (autoSaveTimer != null) clearTimeout(autoSaveTimer)\r\n autoSaveTimer = setTimeout(() => {\r\n autoSaveTimer = null\r\n void enqueueSave().catch((err: unknown) => {\r\n bus.emit('error', { scope: 'auto-save', error: err })\r\n })\r\n }, autoSave.intervalMs ?? 2000)\r\n }\r\n\r\n /** 把一次保存排进队列,返回该次保存的结果 */\r\n function enqueueSave(): Promise<SaveAsResult> {\r\n const run = saveChain.then(() => writeProject())\r\n // 队列本身吞掉异常,避免一次失败让后续保存全部短路\r\n saveChain = run.catch(() => ({ ok: false }))\r\n return run\r\n }\r\n\r\n /** 真正写盘 + 广播 project:saved */\r\n async function writeProject(): Promise<SaveAsResult> {\r\n const { port, ns } = saveTarget()\r\n // 记录写盘开始时的工程引用:写盘期间若又发生变更,引用会变,\r\n // 此时绝不能 markSaved(否则这段时间的编辑会被当成「已保存」而永久丢失)。\r\n const projectAtStart = store.getState().project\r\n const result: SaveResult = await port.save(ns, editor.serialize())\r\n const changedDuringSave = store.getState().project !== projectAtStart\r\n if (result.ok) {\r\n if (!changedDuringSave) store.getState().markSaved()\r\n // 写盘期间有新变更 → 重新排一次,确保最终落到最新内容\r\n else scheduleAutoSave()\r\n bus.emit('project:saved', result.path != null ? { ok: true, path: result.path } : { ok: true })\r\n } else {\r\n bus.emit('project:saved', {\r\n ok: false,\r\n ...(result.error != null ? { error: result.error } : {}),\r\n ...(result.path != null ? { path: result.path } : {}),\r\n })\r\n }\r\n return result\r\n }\r\n\r\n /** 卸载 / 关闭前把待写的防抖任务立刻执行 */\r\n function flushAutoSave(): Promise<SaveAsResult> {\r\n if (autoSaveTimer != null) {\r\n clearTimeout(autoSaveTimer)\r\n autoSaveTimer = null\r\n }\r\n return enqueueSave()\r\n }\r\n\r\n // 保留退订句柄:dispose 时必须解除,否则 store 会通过闭包一直持有已销毁实例\r\n const unsubscribeStore = store.subscribe((state) => {\r\n // dispose 之后 store 仍可被外部写入,但不应再向已销毁的实例广播\r\n if (disposed) return\r\n if (state.project !== prevProject) {\r\n prevProject = state.project\r\n bus.emit('project:changed', { project: state.project, dirty: state.dirty })\r\n // 每次结构性变更都**重置**防抖计时器(而不是只在 dirty 边沿调度):\r\n // 否则第一次变更后固定 intervalMs 就写盘,持续编辑中途会写一次多余的文件。\r\n if (autoSave != null) scheduleAutoSave()\r\n }\r\n if (state.dirty !== prevDirty) {\r\n prevDirty = state.dirty\r\n bus.emit('project:dirty', { dirty: state.dirty })\r\n }\r\n if (state.assets !== prevAssets) {\r\n prevAssets = state.assets\r\n bus.emit('assets:changed', { assets: state.assets })\r\n }\r\n if (state.selectedClipIds !== prevSelection || state.selectedAssetId !== prevAssetId) {\r\n prevSelection = state.selectedClipIds\r\n prevAssetId = state.selectedAssetId\r\n bus.emit('selection:changed', {\r\n clipIds: state.selectedClipIds,\r\n assetId: state.selectedAssetId,\r\n })\r\n }\r\n if (state.playheadUs !== prevPlayhead || state.playing !== lastPlaying) {\r\n prevPlayhead = state.playheadUs\r\n lastPlaying = state.playing\r\n bus.emit('playhead:changed', { us: state.playheadUs, playing: state.playing })\r\n }\r\n const canUndo = history.getState().canUndo()\r\n if (canUndo !== prevCanUndo) {\r\n prevCanUndo = canUndo\r\n bus.emit('history:changed', {\r\n canUndo,\r\n canRedo: history.getState().canRedo(),\r\n })\r\n }\r\n })\r\n\r\n const commands: EditorCommands = {\r\n updateClip: (clipId, patch, recordHistory, mergeKey) =>\r\n store.getState().updateClip(clipId, patch, recordHistory, mergeKey),\r\n moveClip: (clipId, trackId, startUs, recordHistory) =>\r\n store.getState().moveClip(clipId, trackId, startUs, recordHistory),\r\n trimClip: (clipId, edge, deltaUs, recordHistory) =>\r\n store.getState().trimClip(clipId, edge, deltaUs, recordHistory),\r\n splitClipsAt: (atUs, clipIds) => store.getState().splitClipsAt(atUs, clipIds),\r\n duplicateSelection: (offsetUs) => store.getState().duplicateSelection(offsetUs),\r\n addTrack: (kind) => store.getState().addTrack(kind),\r\n removeTrack: (trackId) => store.getState().removeTrack(trackId),\r\n updateTrack: (trackId, patch) => store.getState().updateTrack(trackId, patch),\r\n }\r\n\r\n const editor: CutEditorHandle = {\r\n id,\r\n store,\r\n history,\r\n runtime,\r\n bridge,\r\n get storage() {\r\n return saveTarget().port\r\n },\r\n events: bus,\r\n commands,\r\n\r\n // —— 工程 ——\r\n getProject: () => store.getState().project,\r\n getProjectId: () => store.getState().project.id,\r\n\r\n setProjectId(nextId) {\r\n if (nextId === store.getState().project.id) return\r\n store.getState().setProjectId(nextId)\r\n },\r\n\r\n renameProject: (name) => store.getState().renameProject(name),\r\n setProjectSize: (width, height, mode) => store.getState().setProjectSize(width, height, mode),\r\n getAssets: () => store.getState().assets,\r\n getState: () => store.getState(),\r\n\r\n setProject(project, assets, missingAssetIds = []) {\r\n store.getState().setProject(project, assets, missingAssetIds)\r\n history.getState().clear()\r\n },\r\n\r\n serialize: () => serializeProject(store.getState().project, store.getState().assets),\r\n exportJson: () => serializeProject(store.getState().project, store.getState().assets),\r\n\r\n async load(input) {\r\n if ('schemaVersion' in input) {\r\n const { project, assets, missingAssetIds } = deserializeProject(input)\r\n editor.setProject(project, assets, missingAssetIds)\r\n bus.emit('project:loaded', {\r\n project,\r\n assetCount: assets.length,\r\n missing: missingAssetIds.length,\r\n })\r\n return\r\n }\r\n editor.setProject(input, [])\r\n bus.emit('project:loaded', { project: input, assetCount: 0, missing: 0 })\r\n },\r\n\r\n async importJson(data: SerializedProject, importOptions: ImportJsonOptions = {}) {\r\n const { project, assets, missingAssetIds } = deserializeProject(data)\r\n editor.setProject(project, assets, missingAssetIds)\r\n\r\n const stillMissing = new Set(missingAssetIds)\r\n if (importOptions.resolveFile != null) {\r\n const { importFile } = await loadImportPipeline()\r\n for (const asset of assets) {\r\n if (asset.kind === 'text' || asset.kind === 'subtitle') continue\r\n let file: File | null\r\n try {\r\n file = await importOptions.resolveFile(asset)\r\n } catch (err) {\r\n bus.emit('error', { scope: 'import-json:resolve-file', error: err })\r\n continue\r\n }\r\n if (file == null) continue\r\n try {\r\n // 复用原素材 id:时间轴上的 clip 无需改动即可恢复\r\n const result = await importFile(runtime.clips, file, undefined, { id: asset.id })\r\n // 元数据里的 thumbnailUrls 是会话内 object URL,恢复后必须重抽,\r\n // 否则素材卡片永远停在图标占位(用户看到的就是「资源坏了」)\r\n const { regenerateDerived } = await loadImportPipeline()\r\n const withDerived = await regenerateDerived(runtime.clips, result.asset, file)\r\n store.getState().replaceAsset(asset.id, { ...withDerived, id: asset.id })\r\n stillMissing.delete(asset.id)\r\n } catch (err) {\r\n bus.emit('error', { scope: 'import-json:reconnect', error: err })\r\n }\r\n }\r\n }\r\n\r\n const remaining = [...stillMissing]\r\n store.getState().setMissingAssetIds(remaining)\r\n bus.emit('project:loaded', {\r\n project: store.getState().project,\r\n assetCount: assets.length,\r\n missing: remaining.length,\r\n })\r\n return { missingAssetIds: remaining }\r\n },\r\n\r\n async save(): Promise<SaveAsResult> {\r\n return enqueueSave()\r\n },\r\n\r\n async saveAs(nextStorage, nextNamespace): Promise<SaveAsResult> {\r\n storage = nextStorage\r\n namespace = nextNamespace\r\n if (autoSave != null) autoSave = { ...autoSave, storage: nextStorage, namespace: nextNamespace }\r\n return enqueueSave()\r\n },\r\n\r\n isDirty: () => store.getState().dirty,\r\n isEmpty: () => store.getState().project.clips.length === 0,\r\n\r\n // —— 素材 ——\r\n async addFiles(files: File[], addOptions: AddFilesOptions = {}): Promise<AddFilesResult> {\r\n const { importFiles } = await loadImportFiles()\r\n return importFiles(runtime.clips, store, files, addOptions)\r\n },\r\n\r\n async addTextAsset(params: AddTextAssetParams): Promise<MediaAsset> {\r\n const { createTextAsset } = await loadImportPipeline()\r\n const { project } = store.getState()\r\n const { asset } = createTextAsset({\r\n registry: runtime.clips,\r\n text: params.text,\r\n width: project.width,\r\n height: project.height,\r\n ...(params.style != null ? { style: params.style } : {}),\r\n ...(params.durationUs != null ? { durationUs: params.durationUs } : {}),\r\n })\r\n store.getState().addAsset(asset)\r\n return asset\r\n },\r\n\r\n async addSubtitleAsset(params: AddSubtitleAssetParams): Promise<MediaAsset> {\r\n const { createSubtitleAsset } = await loadImportPipeline()\r\n const { parseSrtDurationUs } = await import('../media/srt')\r\n const { project } = store.getState()\r\n const durationUs =\r\n params.durationUs != null && params.durationUs > 0\r\n ? params.durationUs\r\n : parseSrtDurationUs(params.srt)\r\n const { asset } = createSubtitleAsset({\r\n registry: runtime.clips,\r\n srt: params.srt,\r\n width: project.width,\r\n height: project.height,\r\n ...(durationUs > 0 ? { durationUs } : {}),\r\n ...(params.style != null ? { style: params.style } : {}),\r\n })\r\n store.getState().addAsset(asset)\r\n return asset\r\n },\r\n\r\n async reconnectAsset(assetId, file) {\r\n const { importFile } = await loadImportPipeline()\r\n if (!store.getState().assets.some((a) => a.id === assetId)) {\r\n throw new Error(`素材不存在:${assetId}`)\r\n }\r\n const result = await importFile(runtime.clips, file, undefined, { id: assetId })\r\n const { regenerateDerived } = await loadImportPipeline()\r\n const withDerived = await regenerateDerived(runtime.clips, result.asset, file)\r\n store.getState().replaceAsset(assetId, { ...withDerived, id: assetId })\r\n store.getState().clearMissing(assetId)\r\n },\r\n\r\n getMissingAssetIds: () => [...store.getState().missingAssetIds],\r\n\r\n removeAsset(assetId) {\r\n runtime.clips.removeAsset(assetId)\r\n store.getState().removeAsset(assetId)\r\n },\r\n\r\n // —— 播放 ——\r\n play() {\r\n const { project, playing } = store.getState()\r\n if (playing) return\r\n const total = getProjectDuration(project)\r\n const from = total > 0 && store.getState().playheadUs >= total ? 0 : store.getState().playheadUs\r\n runtime.clock.play(from)\r\n store.getState().setPlaying(true)\r\n store.getState().setPlayhead(from)\r\n },\r\n\r\n pause() {\r\n runtime.clock.pause()\r\n runtime.audio.stopAll()\r\n store.getState().setPlaying(false)\r\n store.getState().setPlayhead(runtime.clock.positionUs)\r\n },\r\n\r\n toggle() {\r\n if (store.getState().playing) editor.pause()\r\n else editor.play()\r\n },\r\n\r\n seek(us) {\r\n const total = getProjectDuration(store.getState().project)\r\n const clamped = Math.max(0, total > 0 ? Math.min(us, total) : us)\r\n runtime.clock.seek(clamped)\r\n store.getState().setPlayhead(clamped)\r\n },\r\n\r\n getPlayhead: () => store.getState().playheadUs,\r\n getDuration: () => getProjectDuration(store.getState().project),\r\n isPlaying: () => store.getState().playing,\r\n\r\n // —— 编辑 ——\r\n undo: () => store.getState().undo(),\r\n redo: () => store.getState().redo(),\r\n canUndo: () => history.getState().canUndo(),\r\n canRedo: () => history.getState().canRedo(),\r\n addClip: (clip: TimelineClip) => store.getState().addClip(clip),\r\n removeClips: (ids) => store.getState().removeClips(ids),\r\n selectClips: (ids) => store.getState().selectClips(ids),\r\n getSelectedClipIds: () => [...store.getState().selectedClipIds],\r\n setSelection(clipIds, assetId) {\r\n store.getState().selectClips(clipIds)\r\n if (assetId !== undefined) store.getState().selectAsset(assetId)\r\n },\r\n updateClip: (clipId, patch, recordHistory, mergeKey) =>\r\n store.getState().updateClip(clipId, patch, recordHistory, mergeKey),\r\n moveClip: (clipId, trackId, startUs, recordHistory) =>\r\n store.getState().moveClip(clipId, trackId, startUs, recordHistory),\r\n trimClip: (clipId, edge, deltaUs, recordHistory) =>\r\n store.getState().trimClip(clipId, edge, deltaUs, recordHistory),\r\n splitClipsAt: (atUs, clipIds) => store.getState().splitClipsAt(atUs, clipIds),\r\n duplicateSelection: (offsetUs) => store.getState().duplicateSelection(offsetUs),\r\n addTrack: (kind: Track['kind']) => store.getState().addTrack(kind),\r\n removeTrack: (trackId) => store.getState().removeTrack(trackId),\r\n updateTrack: (trackId, patch) => store.getState().updateTrack(trackId, patch),\r\n\r\n // —— 导出 ——\r\n export(request: ExportRequest = {}): ExportHandle {\r\n const snapshot = store.getState()\r\n const settings = resolveExportSettings(snapshot.project, request.settings)\r\n const mimeType = request.mimeType ?? 'video/mp4'\r\n const filename = request.filename ?? createExportFilename()\r\n\r\n bus.emit('export:start', {\r\n totalUs: getProjectDuration(snapshot.project),\r\n settings,\r\n })\r\n\r\n let cancelled = false\r\n let inner: { cancel: () => void } | null = null\r\n\r\n const cancel = () => {\r\n cancelled = true\r\n inner?.cancel()\r\n }\r\n\r\n const promise = (async (): Promise<ExportResult> => {\r\n try {\r\n const { startExport, computeExportDuration } = await loadExportRunner()\r\n if (cancelled) throw new ExportCancelledError()\r\n\r\n const handle = startExport(\r\n {\r\n registry: runtime.clips,\r\n project: snapshot.project,\r\n assets: snapshot.assets,\r\n settings,\r\n maxTimeUs: computeExportDuration(snapshot.project),\r\n mimeType,\r\n filename,\r\n ...(bridge.createExportWorker != null\r\n ? {\r\n createWorker: () => {\r\n try {\r\n return bridge.createExportWorker?.() ?? null\r\n } catch (err) {\r\n bus.emit('error', { scope: 'export:worker', error: err })\r\n return null\r\n }\r\n },\r\n }\r\n : {}),\r\n },\r\n (progress) => {\r\n if (!cancelled) bus.emit('export:progress', { progress })\r\n },\r\n )\r\n inner = handle\r\n\r\n const result = await handle.promise\r\n if (cancelled) throw new ExportCancelledError()\r\n // 顶层 blob / filename 是给旧订阅方的兼容副本,新代码请用 result\r\n bus.emit('export:done', { result, blob: result.blob, filename: result.filename })\r\n return result\r\n } catch (err) {\r\n if (cancelled || err instanceof ExportCancelledError) {\r\n // 用户取消不是错误:不广播 export:error\r\n throw err instanceof ExportCancelledError ? err : new ExportCancelledError()\r\n }\r\n bus.emit('export:error', { message: errorMessage(err), error: err })\r\n throw err\r\n }\r\n })()\r\n\r\n return { promise, cancel }\r\n },\r\n\r\n async saveExport(result: ExportResult, suggestedName?: string): Promise<SaveFileResult> {\r\n const saveBlob = bridge.files?.saveBlob\r\n if (saveBlob == null) {\r\n return { ok: false, error: '宿主未提供 saveBlob,无法保存成片' }\r\n }\r\n try {\r\n return await saveBlob.call(bridge.files, result.blob, suggestedName ?? result.filename)\r\n } catch (err) {\r\n return { ok: false, error: errorMessage(err) }\r\n }\r\n },\r\n\r\n // —— 环境 ——\r\n getCapabilities(): Promise<CapabilityReport> {\r\n capabilityPromise ??= detectEnvironment()\r\n return capabilityPromise\r\n },\r\n\r\n // —— 生命周期 ——\r\n async dispose() {\r\n if (disposed) return\r\n disposed = true\r\n // 关闭前把待写的自动保存落盘一次(只读 store,不广播新事件)\r\n const pending = flushAutoSave()\r\n unsubscribeStore()\r\n bus.emit('dispose', {})\r\n await runtime.dispose()\r\n try {\r\n await pending\r\n } catch {\r\n // 保存失败不应阻断销毁\r\n }\r\n bus.clear()\r\n },\r\n }\r\n\r\n return editor\r\n}\r\n","/**\r\n * 素材导入流水线(供 feature 与 Lab 共用)。\r\n *\r\n * 步骤:校验类型 → 创建母版 IClip → 读 meta → 抽缩略图 / 波形 → 写入 registry 与 store。\r\n *\r\n * ⚠️ 所有 WebAV 调用都落在 core 内;本文件位于 core/webav 下,允许 import '@webav/*'。\r\n */\r\n\r\nimport { AudioClip } from '@webav/av-cliper'\r\nimport type { IClip } from '@webav/av-cliper'\r\nimport type { MP4Clip } from '@webav/av-cliper'\r\nimport type { MediaAsset, TextStyle } from '../../types'\r\nimport { detectFileType, fileToStream, UnsupportedFileError } from '../../media/file-reader'\r\nimport { generateThumbnails } from '../../media/thumbnails'\r\nimport { computeWaveform } from '../../media/waveform'\r\nimport { DEFAULT_STILL_DURATION_US } from '../../constants'\r\nimport { asUs } from '../../timeline/time'\r\nimport { createId } from '../../lib/id'\r\nimport { createClipFromFile, createSubtitleClip, createTextClip } from './clip-factory'\r\nimport { DEFAULT_SUBTITLE_STYLE, DEFAULT_TEXT_STYLE } from './styles'\r\nimport type { ClipRegistry } from './clip-registry'\r\n\r\nexport interface ImportedAsset {\r\n asset: MediaAsset\r\n clipId: string\r\n /** 母版 clip(重连素材时用于替换旧的母版) */\r\n master?: IClip\r\n}\r\n\r\nconst THUMBNAIL_COUNT = 4\r\nconst THUMBNAIL_WIDTH = 120\r\nconst WAVEFORM_BUCKETS = 400\r\n\r\nexport interface ImportFileOptions {\r\n /**\r\n * 指定素材 id(宿主把自己的素材记录 id 作为 `MediaAsset.id`)。\r\n *\r\n * 传了就必须保证会话内唯一:重复注册会覆盖 registry 里的旧素材。\r\n */\r\n id?: string\r\n}\r\n\r\n/**\r\n * 导入单个文件,生成素材记录。\r\n *\r\n * @param onProgress 0~1 进度(用于卡片骨架/进度条)\r\n * @throws UnsupportedFileError 类型不支持时\r\n */\r\nexport async function importFile(\r\n registry: ClipRegistry,\r\n file: File,\r\n onProgress?: (progress: number) => void,\r\n options: ImportFileOptions = {},\r\n): Promise<ImportedAsset> {\r\n const detected = detectFileType(file)\r\n onProgress?.(0.1)\r\n\r\n const { clip, meta } = await createClipFromFile(file, detected.kind)\r\n onProgress?.(0.4)\r\n\r\n const assetId = options.id ?? createId('asset')\r\n const asset: MediaAsset = {\r\n id: assetId,\r\n name: file.name,\r\n kind: detected.kind,\r\n mimeType: detected.mimeType,\r\n size: file.size,\r\n durationUs: asUs(meta.durationUs),\r\n width: meta.width,\r\n height: meta.height,\r\n hasAudio: meta.hasAudio,\r\n createdAt: Date.now(),\r\n }\r\n\r\n if (detected.kind === 'video') {\r\n try {\r\n const frames = await generateThumbnails(\r\n clip as MP4Clip,\r\n assetId,\r\n THUMBNAIL_COUNT,\r\n meta.durationUs,\r\n THUMBNAIL_WIDTH,\r\n )\r\n asset.thumbnailUrls = frames.map((f) => f.url)\r\n } catch (err) {\r\n // 抽帧失败不应阻断导入:给出警告但保留素材\r\n console.warn('[importFile] 缩略图生成失败', err)\r\n }\r\n onProgress?.(0.7)\r\n }\r\n\r\n if (detected.kind === 'audio' || (detected.kind === 'video' && meta.hasAudio)) {\r\n try {\r\n if (detected.kind === 'audio') {\r\n const pcm = (clip as AudioClip).getPCMData()\r\n asset.waveform = computeWaveform(pcm, WAVEFORM_BUCKETS)\r\n }\r\n } catch (err) {\r\n console.warn('[importFile] 波形生成失败', err)\r\n }\r\n onProgress?.(0.85)\r\n }\r\n\r\n registry.registerAsset(asset, file, clip)\r\n onProgress?.(1)\r\n return { asset, clipId: assetId, master: clip }\r\n}\r\n\r\n/**\r\n * 为一个「已有元数据」的素材重新生成派生数据(缩略图 / 波形)。\r\n *\r\n * 用于重新打开工程后 `resolveFile` / `reconnectAsset` 找回文件时:\r\n * 元数据里不含 thumbnailUrls(那是会话内 object URL,刷新即失效),\r\n * 如果不补这一步,素材卡片会一直显示图标占位 —— 用户看到的就是「资源坏了」。\r\n *\r\n * 失败只记警告:派生数据缺失不影响素材可用性。\r\n */\r\nexport async function regenerateDerived(\r\n registry: ClipRegistry,\r\n asset: MediaAsset,\r\n file: File,\r\n): Promise<MediaAsset> {\r\n const master = registry.getMaster(asset.id)\r\n if (master == null) return asset\r\n\r\n const next: MediaAsset = { ...asset }\r\n if (asset.kind === 'video') {\r\n const { regenerateThumbnails } = await import('../../media/thumbnails')\r\n try {\r\n const frames = await regenerateThumbnails(\r\n master as MP4Clip,\r\n asset.id,\r\n THUMBNAIL_COUNT,\r\n asset.durationUs,\r\n THUMBNAIL_WIDTH,\r\n )\r\n next.thumbnailUrls = frames.map((f) => f.url)\r\n } catch (err) {\r\n console.warn('[regenerateDerived] 缩略图生成失败', asset.id, err)\r\n }\r\n }\r\n\r\n if (asset.kind === 'audio' || (asset.kind === 'video' && asset.hasAudio)) {\r\n try {\r\n if (asset.kind === 'audio') {\r\n const pcm = (master as AudioClip).getPCMData()\r\n next.waveform = computeWaveform(pcm, WAVEFORM_BUCKETS)\r\n }\r\n } catch (err) {\r\n console.warn('[regenerateDerived] 波形生成失败', asset.id, err)\r\n }\r\n }\r\n void file\r\n return next\r\n}\r\n\r\n/** 新建文字素材(非文件来源,无需二进制) */\r\nexport function createTextAsset(params: {\r\n registry: ClipRegistry\r\n text: string\r\n style?: Partial<TextStyle>\r\n width: number\r\n height: number\r\n durationUs?: number\r\n /** 指定素材 id;不传则生成 `asset_xxx` */\r\n id?: string\r\n}): ImportedAsset {\r\n const assetId = params.id ?? createId('asset')\r\n const style: TextStyle = { ...DEFAULT_TEXT_STYLE, ...params.style }\r\n const durationUs = params.durationUs ?? DEFAULT_STILL_DURATION_US\r\n\r\n const master = createTextClip({\r\n text: params.text,\r\n style,\r\n width: params.width,\r\n height: params.height,\r\n durationUs,\r\n })\r\n\r\n const asset: MediaAsset = {\r\n id: assetId,\r\n name: params.text.slice(0, 24) || '文字',\r\n kind: 'text',\r\n mimeType: 'text/x-title',\r\n size: 0,\r\n durationUs: asUs(durationUs),\r\n width: params.width,\r\n height: params.height,\r\n hasAudio: false,\r\n createdAt: Date.now(),\r\n text: params.text,\r\n textStyle: style,\r\n }\r\n\r\n params.registry.registerAsset(asset, null, master)\r\n return { asset, clipId: assetId }\r\n}\r\n\r\n/**\r\n * 解码素材的完整 PCM(48kHz 双声道),供 Worker 导出使用。\r\n *\r\n * ⚠️ 实测:Worker 内没有 AudioContext / OfflineAudioContext,\r\n * `new AudioClip(stream)` 会在 `decodeAudioData` 处抛错,因此必须在主线程解码后\r\n * 把 Float32Array 传过去(`new AudioClip(Float32Array[])` 走纯归一化分支)。\r\n *\r\n * @returns 声道数组;失败或素材无音轨时返回 undefined\r\n */\r\nexport async function loadPcmForAsset(\r\n registry: ClipRegistry,\r\n assetId: string,\r\n): Promise<Float32Array[] | undefined> {\r\n const asset = registry.getAsset(assetId)\r\n if (asset == null) return undefined\r\n const file = registry.getFile(assetId)\r\n if (file == null) return undefined\r\n try {\r\n // 视频素材也能用 decodeAudioData 解析(内部解码 AAC 音轨)\r\n const clip = new AudioClip(fileToStream(file))\r\n await clip.ready\r\n const pcm = clip.getPCMData().map((channel) => new Float32Array(channel))\r\n clip.destroy()\r\n return pcm\r\n } catch (err) {\r\n console.warn('[loadPcmForAsset] 音频解码失败', err)\r\n return undefined\r\n }\r\n}\r\n\r\n/** 解析素材的音频 PCM(供预览音频引擎注册) */\r\nexport async function loadAudioClipForAsset(\r\n registry: ClipRegistry,\r\n assetId: string,\r\n): Promise<AudioClip | null> {\r\n const file = registry.getFile(assetId)\r\n if (file == null) return null\r\n const asset = registry.getAsset(assetId)\r\n if (asset == null) return null\r\n if (asset.kind !== 'audio' && asset.kind !== 'video') return null\r\n const clip = new AudioClip(fileToStream(file))\r\n await clip.ready\r\n return clip\r\n}\r\n\r\n/** 用第一条字幕文本给素材命名(无内容时回退为「字幕」) */\r\nfunction subtitleName(srt: string): string {\r\n const firstLine =\r\n srt\r\n .split(/\\r?\\n/)\r\n .map((line) => line.trim())\r\n .find((line) => line.length > 0 && !line.includes('-->') && !/^\\d+$/.test(line)) ?? ''\r\n const base = firstLine || '字幕'\r\n return base.length > 20 ? `${base.slice(0, 20)}…` : base\r\n}\r\n\r\n/** 新建字幕素材(内容来自粘贴的 SRT,无源文件) */\r\nexport function createSubtitleAsset(params: {\r\n registry: ClipRegistry\r\n srt: string\r\n width: number\r\n height: number\r\n durationUs?: number\r\n style?: Partial<import('../../types').SubtitleStyle>\r\n /** 指定素材 id;不传则生成 `asset_xxx` */\r\n id?: string\r\n}): ImportedAsset {\r\n const assetId = params.id ?? createId('asset')\r\n const style = { ...DEFAULT_SUBTITLE_STYLE, ...params.style }\r\n const durationUs = params.durationUs ?? DEFAULT_STILL_DURATION_US\r\n\r\n const master = createSubtitleClip({\r\n srt: params.srt,\r\n style,\r\n width: params.width,\r\n height: params.height,\r\n })\r\n\r\n const asset: MediaAsset = {\r\n id: assetId,\r\n name: subtitleName(params.srt),\r\n kind: 'subtitle',\r\n mimeType: 'text/plain',\r\n size: params.srt.length,\r\n durationUs: asUs(durationUs),\r\n width: params.width,\r\n height: params.height,\r\n hasAudio: false,\r\n createdAt: Date.now(),\r\n srt: params.srt,\r\n subtitleStyle: style,\r\n }\r\n\r\n params.registry.registerAsset(asset, null, master)\r\n return { asset, clipId: assetId }\r\n}\r\n\r\nexport { UnsupportedFileError }\r\n","/**\n * 批量导入素材(core 的唯一实现)。\n *\n * 为什么放在 core:导入语义(重连缺失素材、id 注入、失败汇总、串行解码)\n * 被三处调用 —— `<CutWorkbench>` 的 `useMediaImport`、`initCutWorkbench().addFiles`、\n * 以及 `editor.addFiles`。过去 workbench 里另有一份实现,行为与 init 那份不一致\n * (init 不重连、不报进度),宿主换个入口就得到不同结果。\n *\n * 本文件位于 `engine/webav` 下,因此允许 import `@webav/*`(经 importFile 间接)。\n * 编辑器句柄会**动态**加载它,宿主不需要自己写动态 import。\n */\n\nimport type { StoreApi } from 'zustand/vanilla'\nimport type { MediaAsset } from '../../types'\nimport { detectFileType, UnsupportedFileError } from '../../media/file-reader'\nimport { errorMessage } from '../../lib/error'\nimport type { ClipRegistry } from './clip-registry'\nimport { importFile } from './import-pipeline'\n\n/**\n * 导入所需的编辑器状态切片。\n *\n * 引擎层只需要「读素材表 / 缺失列表」与「写回素材 / 选择 / 缺失标记」,\n * 不应依赖完整的 `EditorState`。收窄成这个端口后,editor-store 增删无关字段\n * 不再波及引擎,也避免了引擎能读写 `dialog`、`playheadUs` 之类的 UI 状态。\n */\nexport type ImportTargetState = {\n assets: MediaAsset[]\n missingAssetIds: string[]\n replaceAsset: (assetId: string, next: MediaAsset) => void\n clearMissing: (assetId: string) => void\n addAsset: (asset: MediaAsset) => void\n selectAsset: (id: string | null) => void\n}\n\nexport type ImportTargetStore = Pick<StoreApi<ImportTargetState>, 'getState'>\n\nexport interface ImportFilesOptions {\n /** 导入成功后选中第一个素材;默认 true */\n select?: boolean\n /** 逐文件进度:index 为入参数组下标,progress 为 0~1 */\n onProgress?: (index: number, progress: number) => void\n /** 由宿主指定素材 id;返回 falsy 则回退到库生成的 `asset_xxx` */\n idFor?: (file: File, index: number) => string | null | undefined\n /** 与缺失源文件的素材同名同类型时重连而非新增;默认 true */\n reconnect?: boolean\n}\n\nexport interface ImportFilesResult {\n imported: MediaAsset[]\n failures: Array<{ name: string; error: string }>\n /** 其中走重连的数量 */\n reconnected: number\n}\n\n/** 找出可以「重连」的素材 id(同名 + 同类型 + 当前确实缺少二进制) */\nfunction findReconnectableId(\n state: ImportTargetState,\n registry: ClipRegistry,\n file: File,\n kind: MediaAsset['kind'],\n): string | null {\n if (state.missingAssetIds.length === 0) return null\n const candidate = state.assets.find(\n (asset) =>\n state.missingAssetIds.includes(asset.id) &&\n asset.kind === kind &&\n asset.name === file.name &&\n registry.getFile(asset.id) == null,\n )\n return candidate?.id ?? null\n}\n\n/**\n * 批量导入素材。\n *\n * - 串行处理,避免同时开辟多个解码器把内存打爆;\n * - 单个文件失败只影响自身,最后汇总返回;\n * - 进度由调用方通过 `onProgress` 展示(不写 store,避免高频全局重渲染);\n * - 与「缺失源文件」的素材同名同类型时重连,复用原 assetId。\n */\nexport async function importFiles(\n registry: ClipRegistry,\n store: ImportTargetStore,\n files: File[],\n options: ImportFilesOptions = {},\n): Promise<ImportFilesResult> {\n const { select = true, onProgress, idFor, reconnect = true } = options\n const imported: MediaAsset[] = []\n const failures: ImportFilesResult['failures'] = []\n let reconnected = 0\n\n for (let index = 0; index < files.length; index += 1) {\n const file = files[index]\n if (file == null) continue\n try {\n const detected = detectFileType(file)\n const reconnectId = reconnect\n ? findReconnectableId(store.getState(), registry, file, detected.kind)\n : null\n\n // 宿主指定 id 优先;重连时固定复用原 id(时间轴上的 clip 靠它恢复)\n const requestedId = reconnectId ?? idFor?.(file, index) ?? null\n const assetId = requestedId != null && requestedId !== '' ? requestedId : undefined\n\n if (reconnectId != null) {\n // 先释放旧母版与缩略图缓存,让新文件重新抽帧(同 id,缓存会命中旧帧)\n registry.removeAsset(reconnectId)\n }\n\n const result = await importFile(\n registry,\n file,\n (progress) => onProgress?.(index, progress),\n assetId != null ? { id: assetId } : undefined,\n )\n\n if (reconnectId != null) {\n store.getState().replaceAsset(reconnectId, result.asset)\n store.getState().clearMissing(reconnectId)\n imported.push(result.asset)\n reconnected += 1\n } else {\n store.getState().addAsset(result.asset)\n imported.push(result.asset)\n }\n } catch (err) {\n const message = errorMessage(err)\n failures.push({ name: file.name, error: message })\n // 失败时也把进度推满,调用方不用自己判断「这个文件已经结束了」\n onProgress?.(index, 1)\n if (!(err instanceof UnsupportedFileError)) {\n // 非「类型不支持」的异常通常是解码器问题,值得留一条痕迹\n console.warn('[importFiles] 导入失败', file.name, err)\n }\n }\n }\n\n if (select && imported.length > 0) {\n store.getState().selectAsset(imported[0]?.id ?? null)\n }\n\n return { imported, failures, reconnected }\n}\n","/**\n * 时间轴数据 → Sprite 的转换。\n *\n * 关键点(实测自 1.2.8):\n * - `sprite.rect` 坐标系以 sprite 中心为原点,`rect.x/y` 是**左上角**坐标,\n * 但 `BaseSprite._render` 内部用 `center` 做 translate,绘制时以中心为基准;\n * - `sprite.time.duration` 必须有限,否则 Combinator 无法推断结束时间;\n * - `playbackRate !== 1` 时音频是线性插值变速(会变调)。\n */\n\nimport { OffscreenSprite, VisibleSprite } from '@webav/av-cliper'\nimport type { IClip } from '@webav/av-cliper'\nimport type { TimelineClip } from '../../types'\nimport { asUs } from '../../timeline/time'\n\nexport type SpriteKind = 'visible' | 'offscreen'\n\nexport interface SpriteBuildContext {\n /** 该 sprite 的 zIndex(轨道顺序 + 轨道内顺序) */\n zIndex: number\n}\n\n/**\n * 将时间轴 clip 的属性写入 sprite。\n *\n * @param sprite 目标 sprite(VisibleSprite / OffscreenSprite 均可)\n * @param clip 时间轴 clip(时间单位微秒,transform.angle 为角度)\n */\nexport function applyClipToSprite(\n sprite: VisibleSprite | OffscreenSprite,\n clip: TimelineClip,\n): void {\n sprite.rect.x = clip.transform.x\n sprite.rect.y = clip.transform.y\n sprite.rect.w = clip.transform.w\n sprite.rect.h = clip.transform.h\n sprite.rect.angle = (clip.transform.angle * Math.PI) / 180\n sprite.opacity = clip.opacity\n sprite.flip = clip.flip\n sprite.time = {\n offset: asUs(clip.startUs),\n duration: asUs(clip.durationUs),\n playbackRate: clip.playbackRate,\n }\n}\n\nexport function createVisibleSprite(\n instance: IClip,\n clip: TimelineClip,\n ctx: SpriteBuildContext,\n): VisibleSprite {\n const sprite = new VisibleSprite(instance)\n applyClipToSprite(sprite, clip)\n sprite.zIndex = ctx.zIndex\n sprite.visible = true\n /*\n * 关闭 WebAV 内建的选中框与拖拽,由预览层用 DOM 自绘选框。\n *\n * `disabled` 只影响「能否被 AVCanvas 选中/拖动」,不影响 `rect.checkHit`\n * (WebAV 的命中测试不看 interactable),所以画布内点选仍然可用,\n * 只是改由我们自己的 hitTest + 自绘选框负责,样式与性能都可控。\n */\n sprite.interactable = 'disabled'\n return sprite\n}\n\nexport function createOffscreenSprite(\n instance: IClip,\n clip: TimelineClip,\n ctx: SpriteBuildContext,\n): OffscreenSprite {\n const sprite = new OffscreenSprite(instance)\n applyClipToSprite(sprite, clip)\n sprite.zIndex = ctx.zIndex\n return sprite\n}\n\n/**\n * 透明度淡入淡出(预览可见,导出时同样生效)。\n * 仅在 fadeIn/fadeOut 大于 0 时挂动画,避免无谓的逐帧 setter 调用。\n */\nexport function applyOpacityFade(\n sprite: VisibleSprite | OffscreenSprite,\n clip: TimelineClip,\n): void {\n if (clip.fadeInUs <= 0 && clip.fadeOutUs <= 0) return\n const duration = Math.max(1, clip.durationUs)\n const fadeInRatio = clip.fadeInUs > 0 ? Math.min(0.5, clip.fadeInUs / duration) : 0\n const fadeOutRatio = clip.fadeOutUs > 0 ? Math.min(0.5, clip.fadeOutUs / duration) : 0\n const keyFrames: Record<string, { opacity: number }> = {}\n if (fadeInRatio > 0) {\n keyFrames['0%'] = { opacity: 0 }\n keyFrames[`${Math.round(fadeInRatio * 100)}%`] = { opacity: clip.opacity }\n }\n if (fadeOutRatio > 0) {\n keyFrames[`${Math.round((1 - fadeOutRatio) * 100)}%`] = { opacity: clip.opacity }\n keyFrames['100%'] = { opacity: 0 }\n }\n if (Object.keys(keyFrames).length < 2) return\n sprite.setAnimation(keyFrames, { duration, iterCount: 1 })\n}\n","/**\n * 自定义 IClip:给内部 clip 套一层音量包络(音量 + 淡入淡出)。\n *\n * 为什么需要包装而不是直接改 `MP4Clip.opts.volume`:\n * - `MP4Clip` / `AudioClip` 的音量只能在构造时给定,且 `clone()` 会沿用原 opts;\n * - 同一个素材可能被多个时间轴 clip 复用,每个 clip 的音量/淡入淡出都不同;\n * - `Combinator.addSprite()` 内部会 clone sprite,因此必须在「每次 addSprite 用到的实例」\n * 上做包装,才能让每个 clip 独立生效。\n *\n * 单位:时间参数为微秒。\n */\n\nimport type { IClip } from '@webav/av-cliper'\nimport type { ClipMetaBase } from './meta'\n\nexport interface GainClipOptions {\n /** 线性音量 0~1 */\n volume: number\n /** 淡入时长(微秒,相对该 clip 起点) */\n fadeInUs: number\n /** 淡出时长(微秒,相对该 clip 终点) */\n fadeOutUs: number\n /** clip 总时长(微秒) */\n durationUs: number\n /** 播放速率:tick 内部时间 = 时间轴时间 × rate */\n playbackRate: number\n}\n\nexport class GainClip implements IClip {\n readonly ready: Promise<ClipMetaBase>\n private inner: IClip\n private opts: GainClipOptions\n private destroyed = false\n\n constructor(inner: IClip, opts: GainClipOptions) {\n this.inner = inner\n this.opts = opts\n this.ready = inner.ready\n }\n\n get meta(): ClipMetaBase {\n return this.inner.meta\n }\n\n async tick(time: number): Promise<{\n video?: VideoFrame | ImageBitmap | null\n audio?: Float32Array[]\n state: 'done' | 'success'\n }> {\n if (this.destroyed) return { state: 'done' }\n const result = await this.inner.tick(time)\n if (result.audio == null || result.audio.length === 0) return result\n\n const gain = this.gainAt(time)\n if (gain === 1) return result\n return {\n ...result,\n audio: result.audio.map((channel) => {\n const out = new Float32Array(channel.length)\n for (let i = 0; i < channel.length; i += 1) out[i] = (channel[i] ?? 0) * gain\n return out\n }),\n }\n }\n\n /** 计算某个时刻(源时间,微秒)的线性增益 */\n private gainAt(time: number): number {\n const { volume, fadeInUs, fadeOutUs, durationUs, playbackRate } = this.opts\n const base = Math.max(0, Math.min(1, volume))\n const totalUs = Math.max(1, durationUs * (playbackRate > 0 ? playbackRate : 1))\n const fadeIn = Math.min(fadeInUs * (playbackRate > 0 ? playbackRate : 1), totalUs / 2)\n const fadeOut = Math.min(fadeOutUs * (playbackRate > 0 ? playbackRate : 1), totalUs / 2)\n let gain = base\n if (fadeIn > 0 && time < fadeIn) gain *= Math.max(0, time / fadeIn)\n if (fadeOut > 0 && time > totalUs - fadeOut) {\n gain *= Math.max(0, (totalUs - time) / fadeOut)\n }\n return Math.max(0, Math.min(1, gain))\n }\n\n async clone(): Promise<this> {\n const copy = new GainClip(await this.inner.clone(), { ...this.opts })\n await copy.ready\n return copy as this\n }\n\n async split(time: number): Promise<[this, this]> {\n if (time <= 0 || time >= this.opts.durationUs) throw new Error('\"time\" out of bounds')\n const leftInner = await this.inner.clone()\n const rightInner = await this.inner.clone()\n const left = new GainClip(leftInner, { ...this.opts, durationUs: time })\n const right = new GainClip(rightInner, {\n ...this.opts,\n durationUs: this.opts.durationUs - time,\n })\n await Promise.all([left.ready, right.ready])\n return [left as this, right as this]\n }\n\n destroy(): void {\n if (this.destroyed) return\n this.destroyed = true\n this.inner.destroy()\n }\n}\n","/**\n * 导出时的坐标系换算。\n *\n * 时间轴里所有 `clip.transform`(x/y/w/h)都以**工程分辨率**为坐标系\n * (`project.width × project.height`),而导出可以选择 1080p / 720p / 原尺寸。\n * 若直接把工程坐标丢给 Combinator,导出到不同分辨率时素材会停留在左上角\n * 且尺寸不对 —— 这是一个很容易忽略、但一眼就能看出来的 bug。\n *\n * 单位:位置与尺寸是像素,时间为微秒(与本模块无关)。\n */\n\nimport type { ClipTransform, TimelineClip } from '../../types'\n\nexport interface Size {\n width: number\n height: number\n}\n\n/**\n * 把工程坐标下的 transform 等比映射到输出尺寸。\n *\n * 使用同一个缩放系数(分别算宽高再取较小值会改变构图,横竖比不同时更明显),\n * 并按输出画布居中,保证「预览里居中」在导出后依然居中。\n *\n * @param transform 工程坐标系下的变换\n * @param source 工程分辨率\n * @param target 输出分辨率\n */\nexport function mapTransformToOutput(\n transform: ClipTransform,\n source: Size,\n target: Size,\n): ClipTransform {\n const safeSourceW = Math.max(1, source.width)\n const safeSourceH = Math.max(1, source.height)\n const scaleX = target.width / safeSourceW\n const scaleY = target.height / safeSourceH\n // 取较小系数:等比缩放,避免非等比拉伸\n const scale = Math.min(scaleX, scaleY)\n\n const scaledW = transform.w * scale\n const scaledH = transform.h * scale\n\n // 先按比例缩放中心点,再把整体挪到输出画布中心\n const centerX = (transform.x + transform.w / 2) * scaleX\n const centerY = (transform.y + transform.h / 2) * scaleY\n const offsetX = (target.width - safeSourceW * scale) / 2\n const offsetY = (target.height - safeSourceH * scale) / 2\n\n return {\n x: centerX + offsetX - scaledW / 2,\n y: centerY + offsetY - scaledH / 2,\n w: scaledW,\n h: scaledH,\n angle: transform.angle,\n }\n}\n\n/** 便捷版:直接返回换算后的 clip(浅拷贝,只替换 transform) */\nexport function mapClipToOutput(\n clip: TimelineClip,\n source: Size,\n target: Size,\n): TimelineClip {\n return { ...clip, transform: mapTransformToOutput(clip.transform, source, target) }\n}\n","/**\r\n * 导出流水线:时间轴数据 → Combinator → MP4 二进制。\r\n *\r\n * 该模块不依赖 React;`buildCombinator` 同时被主线程与 Worker 复用。\r\n *\r\n * ⚠️ `Combinator.output()` 在当前线程同步步进渲染,会阻塞 UI。\r\n * 主线程路径(`exportProject`)只作为 Worker 不可用时的降级方案,必须配全屏遮罩。\r\n */\r\n\r\nimport { Combinator, fixFMP4Duration } from '@webav/av-cliper'\r\nimport type { IClip } from '@webav/av-cliper'\r\nimport type { MediaAsset, TimelineClip, Track } from '../../types'\r\nimport { clampStillDuration } from '../../timeline/model'\r\nimport { applyOpacityFade, createOffscreenSprite } from './sprite-factory'\r\nimport { GainClip } from './gain-clip'\r\nimport { mapClipToOutput } from './coordinate'\r\n\r\nexport interface ExportClipInput {\r\n clip: TimelineClip\r\n instance: IClip\r\n kind: MediaAsset['kind']\r\n track: Track\r\n}\r\n\r\nexport interface BuildCombinatorOptions {\r\n width: number\r\n height: number\r\n fps: number\r\n bitrate: number\r\n includeAudio: boolean\r\n bgColor?: string\r\n /**\r\n * 工程分辨率。\r\n *\r\n * 时间轴里的 clip.transform 以工程坐标为基准,导出分辨率可能不同\r\n * (1080p / 720p / 原尺寸),因此必须按此缩放,否则素材会挤在左上角。\r\n */\r\n projectWidth?: number\r\n projectHeight?: number\r\n}\r\n\r\nexport interface BuiltCombinator {\r\n combinator: Combinator\r\n /** 实际输出时长上限(微秒) */\r\n maxTimeUs: number\r\n skipped: Array<{ clipId: string; reason: string }>\r\n}\r\n\r\n/**\r\n * 给 clip 套上音量包络。\r\n *\r\n * WebAV 的音频混音是直接相加,没有每轨音量;`MP4Clip` / `AudioClip` 的 volume\r\n * 只能在构造时指定,且 clone 会沿用(同一素材被多个 clip 复用时无法区分)。\r\n * 因此统一用 `GainClip` 包装:每个时间轴 clip 走自己的实例与包络。\r\n *\r\n * @param instance 该 clip 独占的 IClip\r\n * @param clip 时间轴数据(提供 volume / fadeIn / fadeOut)\r\n */\r\nexport function withClipGain(\r\n instance: IClip,\r\n clip: TimelineClip,\r\n options: { trackMuted?: boolean } = {},\r\n): IClip {\r\n const volume = options.trackMuted === true ? 0 : clip.volume\r\n const needsGain =\r\n clip.muted ||\r\n options.trackMuted === true ||\r\n volume !== 1 ||\r\n clip.fadeInUs > 0 ||\r\n clip.fadeOutUs > 0\r\n if (!needsGain) return instance\r\n if (clip.muted) return instance\r\n return new GainClip(instance, {\r\n volume,\r\n fadeInUs: clip.fadeInUs,\r\n fadeOutUs: clip.fadeOutUs,\r\n durationUs: clip.durationUs,\r\n playbackRate: clip.playbackRate,\r\n })\r\n}\r\n\r\n/**\r\n * 组装 Combinator。\r\n *\r\n * - 只加入未隐藏轨道的 clip;`includeAudio === false` 时跳过音频轨;\r\n * - 显式给 `output({ maxTime })`,因此不依赖 main sprite(静态素材不会导致\r\n * \"Unable to determine the end time\");\r\n * - 音频 sprite 不参与画面,但仍需加入才能混合音轨。\r\n */\r\nexport async function buildCombinator(\r\n inputs: ExportClipInput[],\r\n opts: BuildCombinatorOptions,\r\n): Promise<BuiltCombinator> {\r\n const combinator = new Combinator({\r\n width: opts.width,\r\n height: opts.height,\r\n fps: opts.fps,\r\n bitrate: opts.bitrate,\r\n bgColor: opts.bgColor ?? '#000000',\r\n audio: opts.includeAudio ? undefined : false,\r\n })\r\n\r\n const skipped: Array<{ clipId: string; reason: string }> = []\r\n const usable = inputs.filter((item) => !item.track.hidden)\r\n if (usable.length === 0) {\r\n throw new Error('时间轴为空,无法导出')\r\n }\r\n\r\n // 视觉轨在前(zIndex 小,先绘制),音频轨在后;同轨按起点排序\r\n const ordered = [...usable].sort((a, b) => {\r\n const aAudio = a.track.kind === 'audio' ? 1 : 0\r\n const bAudio = b.track.kind === 'audio' ? 1 : 0\r\n if (aAudio !== bAudio) return aAudio - bAudio\r\n if (a.clip.startUs !== b.clip.startUs) return a.clip.startUs - b.clip.startUs\r\n return a.clip.id.localeCompare(b.clip.id)\r\n })\r\n\r\n // 工程坐标 → 输出坐标\r\n const sourceSize = {\r\n width: opts.projectWidth ?? opts.width,\r\n height: opts.projectHeight ?? opts.height,\r\n }\r\n const targetSize = { width: opts.width, height: opts.height }\r\n\r\n let maxTimeUs = 0\r\n let zIndex = 0\r\n\r\n for (const item of ordered) {\r\n const { instance, track } = item\r\n const clip = mapClipToOutput(item.clip, sourceSize, targetSize)\r\n const isAudioTrack = track.kind === 'audio'\r\n if (isAudioTrack && !opts.includeAudio) continue\r\n // 轨道静音只丢音频,不能连画面一起跳过(否则「静音视频轨」导出后整段消失,\r\n // 而预览里画面还在 —— 这是最容易踩到的预览/导出不一致)\r\n const trackMuted = track.muted\r\n if (clip.muted) continue\r\n\r\n const duration = clampStillDuration(clip.durationUs)\r\n if (clip.durationUs <= 0) {\r\n skipped.push({ clipId: clip.id, reason: '时长为 0,已跳过' })\r\n continue\r\n }\r\n\r\n const normalized: TimelineClip = {\r\n ...clip,\r\n durationUs: duration as TimelineClip['durationUs'],\r\n }\r\n const sprite = createOffscreenSprite(\r\n withClipGain(instance, normalized, { trackMuted }),\r\n normalized,\r\n { zIndex },\r\n )\r\n applyOpacityFade(sprite, normalized)\r\n\r\n await combinator.addSprite(sprite, { main: false })\r\n maxTimeUs = Math.max(maxTimeUs, clip.startUs + duration)\r\n zIndex += 1\r\n }\r\n\r\n if (!Number.isFinite(maxTimeUs) || maxTimeUs <= 0) {\r\n throw new Error('无法确定导出时长:请确认时间轴上有有效片段')\r\n }\r\n\r\n return { combinator, maxTimeUs, skipped }\r\n}\r\n\r\nexport interface ExportOptions extends BuildCombinatorOptions {\r\n /** 输出后修复 fMP4 时长(部分播放器读不到 moov 时长) */\r\n fixDuration?: boolean\r\n}\r\n\r\n/**\r\n * 主线程导出(Worker 不可用时的降级路径)。\r\n *\r\n * @param inputs 已构建好的 clip 实例(调用方负责 destroy)\r\n * @param opts 输出配置\r\n * @param onProgress 0~1 进度回调\r\n */\r\nexport async function exportProject(\r\n inputs: ExportClipInput[],\r\n opts: ExportOptions,\r\n onProgress?: (progress: number) => void,\r\n): Promise<{ buffer: ArrayBuffer; elapsedMs: number; skipped: BuiltCombinator['skipped'] }> {\r\n const startedAt = performance.now()\r\n const { combinator, maxTimeUs, skipped } = await buildCombinator(inputs, opts)\r\n\r\n const unsubscribe = onProgress\r\n ? combinator.on('OutputProgress', (p) => onProgress(p))\r\n : () => undefined\r\n\r\n try {\r\n const stream = combinator.output({ maxTime: maxTimeUs })\r\n const raw = await new Response(stream).arrayBuffer()\r\n if (opts.fixDuration) {\r\n const fixed = await fixFMP4Duration(\r\n new Response(raw).body as ReadableStream<Uint8Array>,\r\n )\r\n return {\r\n buffer: await new Response(fixed).arrayBuffer(),\r\n elapsedMs: performance.now() - startedAt,\r\n skipped,\r\n }\r\n }\r\n return { buffer: raw, elapsedMs: performance.now() - startedAt, skipped }\r\n } finally {\r\n unsubscribe()\r\n combinator.destroy()\r\n }\r\n}\r\n\r\n/** 检查分辨率 / 帧率 / 码率组合是否可编码 */\r\nexport async function checkExportSupport(opts: {\r\n width: number\r\n height: number\r\n bitrate: number\r\n}): Promise<boolean> {\r\n return Combinator.isSupported({\r\n width: opts.width,\r\n height: opts.height,\r\n bitrate: opts.bitrate,\r\n })\r\n}\r\n\r\n/** 时间轴结束时间(微秒) */\r\nexport function computeTimelineEnd(clips: TimelineClip[]): number {\r\n return clips.reduce((max, c) => Math.max(max, c.startUs + c.durationUs), 0)\r\n}\r\n","/**\r\n * 导出编排(core 版本)。\r\n *\r\n * 分层:本模块允许 import `@webav/*`(位于 engine/webav 下),但**不**依赖 React,\r\n * 也不做任何 UI。上层(`editor.export()` 与 `<CutWorkbench>` 的导出对话框)\r\n * 都消费同一套逻辑,避免「UI 里能导出、命令式 API 不能导出」的分裂。\r\n *\r\n * 两条路径:\r\n * - 默认走 Worker(不阻塞 UI);\r\n * - 含文字片段(依赖 DOM)或 Worker 不可用时回退主线程;\r\n * - 含音频 / 视频音轨时,主线程会先解码 PCM 再传给 Worker\r\n * (Worker 内没有 AudioContext,AudioClip 无法自行解码)。\r\n */\r\n\r\nimport type { MediaAsset, Project } from '../../types'\r\nimport type { ExportSettings } from '../../types'\r\nimport { ExportCancelledError } from '../../errors'\r\nimport type { ExportResult } from '../../export/types'\r\nimport { createExportFilename } from '../../export/types'\r\nimport type { ClipRegistry } from './clip-registry'\r\nimport {\r\n checkExportSupport,\r\n exportProject,\r\n type ExportClipInput,\r\n} from './exporter'\r\nimport { loadPcmForAsset } from './import-pipeline'\r\nimport type { WorkerAssetPayload, WorkerResponse, WorkerTimelinePayload } from './types'\r\n\r\n/** 标记取消的哨兵:只在本模块内部流转,不暴露给宿主 */\r\nconst CANCELLED = '__export_cancelled__'\r\n\r\nexport interface ExportPlanOptions {\r\n registry: ClipRegistry\r\n /**\r\n * Worker 工厂;返回 null 表示不可用,直接走主线程。\r\n * 不传时使用 `setDefaultExportWorkerFactory` 注册的默认实现(若有)。\r\n */\r\n createWorker?: () => Worker | null\r\n project: Project\r\n assets: MediaAsset[]\r\n settings: ExportSettings\r\n /** 导出上限(微秒),默认全片 */\r\n maxTimeUs?: number\r\n /** 输出 Blob 的 MIME,默认 video/mp4 */\r\n mimeType?: string\r\n filename?: string\r\n}\r\n\r\nexport interface CoreExportHandle {\r\n promise: Promise<ExportResult>\r\n cancel: () => void\r\n}\r\n\r\n/**\r\n * 默认 Worker 工厂(构建形态相关)。\r\n *\r\n * core 不能知道 UI 包把导出 Worker 打成了哪种资源(独立 chunk / UMD 内联),\r\n * 因此由 `@cut-workbench/workbench` 在模块加载时注册一份;宿主若提供了\r\n * `HostBridge.createExportWorker`,那个优先级更高。两者都没有时走主线程。\r\n */\r\nlet defaultWorkerFactory: (() => Worker | null) | null = null\r\n\r\nexport function setDefaultExportWorkerFactory(factory: (() => Worker | null) | null): void {\r\n defaultWorkerFactory = factory\r\n}\r\n\r\n/** 判断是否可以用 Worker 导出(文字素材依赖 DOM) */\r\nexport function canUseWorker(project: Project, assets: MediaAsset[]): boolean {\r\n const kinds = new Map(assets.map((a) => [a.id, a.kind]))\r\n return !project.clips.some((clip) => kinds.get(clip.assetId) === 'text')\r\n}\r\n\r\n/** 参与导出的片段最大结束时间(跳过隐藏轨道) */\r\nexport function computeExportDuration(project: Project, maxTimeUs?: number): number {\r\n if (maxTimeUs != null) return maxTimeUs\r\n const hidden = new Set(project.tracks.filter((t) => t.hidden).map((t) => t.id))\r\n return project.clips.reduce(\r\n (max, clip) => (hidden.has(clip.trackId) ? max : Math.max(max, clip.startUs + clip.durationUs)),\r\n 0,\r\n )\r\n}\r\n\r\n/**\r\n * 构建 Worker 载荷。\r\n *\r\n * 音频素材需要主线程预解码 PCM(Worker 无 AudioContext)。为避免无谓解码,\r\n * 只有「音频轨素材」或「含音轨且会被播放的视频」才解码。\r\n */\r\nexport async function buildWorkerPayload(\r\n registry: ClipRegistry,\r\n project: Project,\r\n assets: MediaAsset[],\r\n settings: ExportSettings,\r\n maxTimeUs?: number,\r\n): Promise<{ assets: WorkerAssetPayload[]; timeline: WorkerTimelinePayload; missing: string[] }> {\r\n const usedAssetIds = new Set(project.clips.map((c) => c.assetId))\r\n\r\n const missing: string[] = []\r\n const payloadAssets: WorkerAssetPayload[] = []\r\n\r\n for (const asset of assets) {\r\n if (!usedAssetIds.has(asset.id)) continue\r\n const file = registry.getFile(asset.id)\r\n if (file == null && asset.kind !== 'text' && asset.kind !== 'subtitle') {\r\n missing.push(asset.name)\r\n continue\r\n }\r\n\r\n let pcm: Float32Array[] | undefined\r\n const needsAudio =\r\n settings.includeAudio &&\r\n (asset.kind === 'audio' || (asset.kind === 'video' && asset.hasAudio))\r\n if (needsAudio) {\r\n pcm = await loadPcmForAsset(registry, asset.id)\r\n }\r\n\r\n payloadAssets.push({\r\n id: asset.id,\r\n kind: asset.kind,\r\n file,\r\n width: asset.width,\r\n height: asset.height,\r\n durationUs: asset.durationUs,\r\n hasAudio: asset.hasAudio,\r\n pcm,\r\n text: asset.text,\r\n textStyle: asset.textStyle,\r\n srt: asset.srt,\r\n subtitleStyle: asset.subtitleStyle,\r\n })\r\n }\r\n\r\n const kindOf = new Map(assets.map((a) => [a.id, a.kind]))\r\n const clips = project.clips.map((clip) => ({\r\n clip,\r\n kind: kindOf.get(clip.assetId) ?? 'video',\r\n }))\r\n\r\n return {\r\n assets: payloadAssets,\r\n timeline: {\r\n width: settings.width,\r\n height: settings.height,\r\n projectWidth: project.width,\r\n projectHeight: project.height,\r\n fps: settings.fps,\r\n bitrate: settings.bitrate,\r\n includeAudio: settings.includeAudio,\r\n bgColor: '#000000',\r\n maxTimeUs,\r\n tracks: project.tracks.map((t) => ({\r\n id: t.id,\r\n kind: t.kind,\r\n hidden: t.hidden,\r\n muted: t.muted,\r\n })),\r\n clips,\r\n },\r\n missing,\r\n }\r\n}\r\n\r\n/**\r\n * 主线程回退导出:为每个 clip 从 registry 建实例后交给 core exporter。\r\n *\r\n * 注意:实例用 `export:<clipId>` 作为临时 key 注册进 registry,导出结束后统一销毁,\r\n * 避免污染预览用的实例(两者不可共用,因为共享 tick 状态)。\r\n */\r\nasync function exportOnMainThread(\r\n options: ExportPlanOptions,\r\n onProgress: (progress: number) => void,\r\n shouldCancel: () => boolean,\r\n): Promise<ExportResult> {\r\n const { registry, project, settings, mimeType = 'video/mp4', filename } = options\r\n const trackMap = new Map(project.tracks.map((t) => [t.id, t]))\r\n const inputs: ExportClipInput[] = []\r\n const skipped: Array<{ clipId: string; reason: string }> = []\r\n const tempIds: string[] = []\r\n\r\n try {\r\n for (const clip of project.clips) {\r\n if (shouldCancel()) throw new ExportCancelledError()\r\n const track = trackMap.get(clip.trackId)\r\n if (!track || track.hidden) continue\r\n const asset = registry.getAsset(clip.assetId)\r\n if (!asset) {\r\n skipped.push({ clipId: clip.id, reason: '素材缺失' })\r\n continue\r\n }\r\n const tempId = `export:${clip.id}`\r\n try {\r\n const instance = await registry.createInstance({\r\n timelineClipId: tempId,\r\n assetId: clip.assetId,\r\n inPointUs: clip.inPointUs,\r\n durationUs: clip.durationUs * clip.playbackRate,\r\n text: clip.text,\r\n textStyle: clip.textStyle,\r\n srt: clip.srt,\r\n subtitleStyle: clip.subtitleStyle,\r\n projectWidth: project.width,\r\n projectHeight: project.height,\r\n })\r\n tempIds.push(tempId)\r\n inputs.push({ clip, instance, kind: asset.kind, track })\r\n } catch (err) {\r\n skipped.push({\r\n clipId: clip.id,\r\n reason: err instanceof Error ? err.message : String(err),\r\n })\r\n }\r\n }\r\n\r\n if (inputs.length === 0) throw new Error('时间轴为空,无法导出')\r\n if (shouldCancel()) throw new ExportCancelledError()\r\n\r\n const startedAt = performance.now()\r\n const { buffer, skipped: buildSkipped } = await exportProject(\r\n inputs,\r\n {\r\n width: settings.width,\r\n height: settings.height,\r\n fps: settings.fps,\r\n bitrate: settings.bitrate,\r\n includeAudio: settings.includeAudio,\r\n projectWidth: project.width,\r\n projectHeight: project.height,\r\n },\r\n onProgress,\r\n )\r\n if (shouldCancel()) throw new ExportCancelledError()\r\n\r\n return {\r\n blob: new Blob([buffer], { type: mimeType }),\r\n filename: filename ?? createExportFilename(),\r\n mimeType,\r\n durationUs: computeExportDuration(project, options.maxTimeUs),\r\n width: settings.width,\r\n height: settings.height,\r\n fps: settings.fps,\r\n bitrate: settings.bitrate,\r\n elapsedMs: performance.now() - startedAt,\r\n skipped: [...skipped, ...buildSkipped],\r\n mode: 'main',\r\n }\r\n } finally {\r\n for (const id of tempIds) registry.destroyInstance(id)\r\n }\r\n}\r\n\r\nasync function exportViaWorker(\r\n options: ExportPlanOptions,\r\n worker: Worker,\r\n onProgress: (progress: number) => void,\r\n shouldCancel: () => boolean,\r\n): Promise<ExportResult> {\r\n const { registry, project, assets, settings, maxTimeUs, mimeType = 'video/mp4', filename } = options\r\n const { assets: payloadAssets, timeline, missing } = await buildWorkerPayload(\r\n registry,\r\n project,\r\n assets,\r\n settings,\r\n maxTimeUs,\r\n )\r\n if (shouldCancel()) throw new ExportCancelledError()\r\n\r\n // 缺源文件的素材由 Worker 逐 clip 跳过(不阻断整次导出);\r\n // 这里只做一次提示,最终原因会出现在 result.skipped 里。\r\n if (missing.length > 0) {\r\n console.warn('[export] 以下素材缺少源文件,相关片段将被跳过:', missing.join('、'))\r\n }\r\n\r\n return new Promise<ExportResult>((resolve, reject) => {\r\n const startedAt = performance.now()\r\n\r\n const cleanup = () => {\r\n worker.onmessage = null\r\n worker.onerror = null\r\n worker.terminate()\r\n }\r\n\r\n worker.onmessage = (event: MessageEvent<WorkerResponse>) => {\r\n const message = event.data\r\n if (message.type === 'progress') {\r\n if (shouldCancel()) return\r\n onProgress(message.progress)\r\n return\r\n }\r\n if (message.type === 'done') {\r\n cleanup()\r\n if (shouldCancel()) {\r\n reject(new ExportCancelledError())\r\n return\r\n }\r\n resolve({\r\n blob: new Blob([message.buffer], { type: mimeType }),\r\n filename: filename ?? createExportFilename(),\r\n mimeType,\r\n durationUs: computeExportDuration(project, maxTimeUs),\r\n width: settings.width,\r\n height: settings.height,\r\n fps: settings.fps,\r\n bitrate: settings.bitrate,\r\n elapsedMs: performance.now() - startedAt,\r\n skipped: message.skipped ?? [],\r\n mode: 'worker',\r\n })\r\n return\r\n }\r\n if (message.type === 'error') {\r\n cleanup()\r\n reject(\r\n message.message === CANCELLED || shouldCancel()\r\n ? new ExportCancelledError()\r\n : new Error(message.message),\r\n )\r\n }\r\n }\r\n\r\n worker.onerror = (event) => {\r\n cleanup()\r\n reject(new Error(event.message || '导出 Worker 异常'))\r\n }\r\n\r\n if (shouldCancel()) {\r\n cleanup()\r\n reject(new ExportCancelledError())\r\n return\r\n }\r\n\r\n worker.postMessage({ type: 'export', assets: payloadAssets, timeline, fixDuration: true })\r\n })\r\n}\r\n\r\n/**\r\n * 启动导出。\r\n *\r\n * 优先走 Worker(不阻塞 UI);含文字片段或 Worker 不可用 / 失败时回退主线程。\r\n * `cancel()` 后 promise 以 `ExportCancelledError` reject。\r\n */\r\nexport function startExport(\r\n options: ExportPlanOptions,\r\n onProgress: (progress: number) => void = () => undefined,\r\n): CoreExportHandle {\r\n const { project, assets, settings } = options\r\n let cancelled = false\r\n let worker: Worker | null = null\r\n\r\n const cancel = () => {\r\n cancelled = true\r\n if (worker != null) {\r\n try {\r\n worker.postMessage({ type: 'cancel' })\r\n } catch {\r\n // Worker 已终止时 postMessage 会抛,忽略即可\r\n }\r\n worker.terminate()\r\n worker = null\r\n }\r\n }\r\n\r\n const promise = (async (): Promise<ExportResult> => {\r\n const supported = await checkExportSupport({\r\n width: settings.width,\r\n height: settings.height,\r\n bitrate: settings.bitrate,\r\n })\r\n if (!supported) {\r\n throw new Error(\r\n `当前设备不支持编码 ${settings.width}×${settings.height} @ ${(settings.bitrate / 1e6).toFixed(1)}Mbps,请降低分辨率或码率`,\r\n )\r\n }\r\n if (cancelled) throw new ExportCancelledError()\r\n\r\n const makeWorker = () => {\r\n const factory = options.createWorker ?? defaultWorkerFactory\r\n if (factory == null) return null\r\n try {\r\n return factory()\r\n } catch (err) {\r\n console.warn('[export] 创建 Worker 失败,回退主线程', err)\r\n return null\r\n }\r\n }\r\n\r\n if (!canUseWorker(project, assets)) {\r\n return exportOnMainThread(options, onProgress, () => cancelled)\r\n }\r\n\r\n const created = makeWorker()\r\n if (created == null) {\r\n return exportOnMainThread(options, onProgress, () => cancelled)\r\n }\r\n\r\n worker = created\r\n try {\r\n return await exportViaWorker(options, created, onProgress, () => cancelled)\r\n } catch (err) {\r\n if (cancelled || err instanceof ExportCancelledError) throw new ExportCancelledError()\r\n console.warn('[export] Worker 导出失败,回退主线程', err)\r\n return exportOnMainThread(options, onProgress, () => cancelled)\r\n }\r\n })()\r\n\r\n return { promise, cancel }\r\n}\r\n","/**\r\n * 时间轴 clip → sprite / clip 实例的同步器。\r\n *\r\n * 预览层需要把「store 里的纯数据」翻译成 WebAV 实例。同步策略:\r\n * - 新增 clip:从 registry 创建实例 + VisibleSprite;\r\n * - 删除 clip:销毁 sprite 与实例;\r\n * - 属性变化:只更新 sprite 属性(不重建,避免闪烁);\r\n * - 结构变化(入点/时长变化导致需要新 IClip):重建实例。\r\n *\r\n * 这里不含 React,由 preview 的 hook 驱动调用。\r\n */\r\n\r\nimport type { VisibleSprite } from '@webav/av-cliper'\r\nimport type { MediaAsset, Project, TimelineClip, Track } from '../../types'\r\nimport type { ClipRegistry } from './clip-registry'\r\nimport { applyClipToSprite, applyOpacityFade, createVisibleSprite } from './sprite-factory'\r\n\r\nexport interface SpriteSyncResult {\r\n /** timelineClipId → sprite */\r\n sprites: Map<string, VisibleSprite>\r\n removed: string[]\r\n created: string[]\r\n errors: Array<{ clipId: string; message: string }>\r\n}\r\n\r\nfunction zIndexOf(tracks: Track[], trackId: string, clip: TimelineClip): number {\r\n const trackIndex = tracks.findIndex((t) => t.id === trackId)\r\n return Math.max(0, trackIndex) * 1000 + Math.round(clip.startUs / 1_000_000)\r\n}\r\n\r\n/**\r\n * 计算需要重建实例的 clip(入点 / 时长变了,旧 IClip 的数据窗口不再匹配)。\r\n */\r\nfunction needsRebuild(previous: TimelineClip | undefined, next: TimelineClip): boolean {\r\n if (!previous) return true\r\n return previous.inPointUs !== next.inPointUs\r\n}\r\n\r\nexport class SpriteSynchronizer {\r\n /** 由创建方注入,保证一个预览画布只操作自己的 clip 实例 */\r\n private readonly registry: ClipRegistry\r\n private sprites = new Map<string, VisibleSprite>()\r\n /** 记录上一次同步的数据,用于检测属性变化 */\r\n private lastClips = new Map<string, TimelineClip>()\r\n\r\n constructor(registry: ClipRegistry) {\r\n this.registry = registry\r\n }\r\n\r\n getSprite(clipId: string): VisibleSprite | undefined {\r\n return this.sprites.get(clipId)\r\n }\r\n\r\n listSprites(): VisibleSprite[] {\r\n return Array.from(this.sprites.values())\r\n }\r\n\r\n /**\r\n * 全量对齐:以 store 的 clips 为准增删 sprite。\r\n *\r\n * @param assets 素材表(用于判断类型)\r\n * @returns 变更详情,调用方据此把 sprite 挂到 AVCanvas\r\n */\r\n async sync(\r\n project: Project,\r\n assets: MediaAsset[],\r\n handlers: {\r\n onAdd: (sprite: VisibleSprite) => Promise<void>\r\n onRemove: (sprite: VisibleSprite) => void\r\n },\r\n ): Promise<SpriteSyncResult> {\r\n const assetMap = new Map(assets.map((a) => [a.id, a]))\r\n const nextIds = new Set(project.clips.map((c) => c.id))\r\n const removed: string[] = []\r\n\r\n for (const [id, sprite] of Array.from(this.sprites)) {\r\n if (nextIds.has(id)) continue\r\n handlers.onRemove(sprite)\r\n this.sprites.delete(id)\r\n this.lastClips.delete(id)\r\n this.registry.destroyInstance(id)\r\n removed.push(id)\r\n }\r\n\r\n const created: string[] = []\r\n const errors: Array<{ clipId: string; message: string }> = []\r\n\r\n for (const clip of project.clips) {\r\n const track = project.tracks.find((t) => t.id === clip.trackId)\r\n if (!track) continue\r\n const asset = assetMap.get(clip.assetId)\r\n if (!asset) {\r\n errors.push({ clipId: clip.id, message: '引用的素材已不存在,请重新导入' })\r\n continue\r\n }\r\n\r\n // 素材元数据在、但二进制不在(刷新后恢复的工程):\r\n // 跳过而不抛错,由调用方汇总一次友好提示。\r\n const needsFile = asset.kind === 'video' || asset.kind === 'audio' || asset.kind === 'image'\r\n if (needsFile && this.registry.getFile(asset.id) == null) {\r\n errors.push({ clipId: clip.id, message: `素材文件未载入:${asset.name}` })\r\n continue\r\n }\r\n\r\n const previous = this.lastClips.get(clip.id)\r\n const existing = this.sprites.get(clip.id)\r\n\r\n if (existing && !needsRebuild(previous, clip)) {\r\n // 仅更新属性\r\n applyClipToSprite(existing, clip)\r\n existing.zIndex = zIndexOf(project.tracks, clip.trackId, clip)\r\n existing.visible = !track.hidden\r\n // 淡入淡出是「属性」而不是结构:预览与导出必须同样生效。\r\n // 只在时长/淡入淡出变化时重新挂动画,避免每帧重建动画对象。\r\n if (\r\n previous == null ||\r\n previous.durationUs !== clip.durationUs ||\r\n previous.fadeInUs !== clip.fadeInUs ||\r\n previous.fadeOutUs !== clip.fadeOutUs ||\r\n previous.opacity !== clip.opacity\r\n ) {\r\n applyOpacityFade(existing, clip)\r\n }\r\n this.lastClips.set(clip.id, clip)\r\n continue\r\n }\r\n\r\n if (existing) {\r\n handlers.onRemove(existing)\r\n this.sprites.delete(clip.id)\r\n this.registry.destroyInstance(clip.id)\r\n }\r\n\r\n try {\r\n // 视频 / 音频轨上的音频 clip 需要独立 IClip;文字/字幕按内容重建\r\n const instance = await this.registry.createInstance({\r\n timelineClipId: clip.id,\r\n assetId: clip.assetId,\r\n inPointUs: clip.inPointUs,\r\n durationUs: clip.durationUs * clip.playbackRate,\r\n text: clip.text,\r\n textStyle: clip.textStyle,\r\n srt: clip.srt,\r\n subtitleStyle: clip.subtitleStyle,\r\n projectWidth: project.width,\r\n projectHeight: project.height,\r\n })\r\n\r\n // 音频轨只提供声音,不绘制画面\r\n const sprite = createVisibleSprite(instance, clip, {\r\n zIndex: zIndexOf(project.tracks, clip.trackId, clip),\r\n })\r\n sprite.visible = !track.hidden\r\n // 预览里同样应用淡入淡出(导出侧在 exporter 里调用同一函数)\r\n applyOpacityFade(sprite, clip)\r\n if (track.kind === 'audio' && asset.kind === 'audio') {\r\n // 纯音频素材没有画面:尺寸置 0,避免 AVCanvas 绘制空内容\r\n sprite.rect.w = 0\r\n sprite.rect.h = 0\r\n }\r\n\r\n await handlers.onAdd(sprite)\r\n this.sprites.set(clip.id, sprite)\r\n this.lastClips.set(clip.id, clip)\r\n created.push(clip.id)\r\n } catch (err) {\r\n errors.push({\r\n clipId: clip.id,\r\n message: err instanceof Error ? err.message : String(err),\r\n })\r\n }\r\n }\r\n\r\n return { sprites: new Map(this.sprites), removed, created, errors }\r\n }\r\n\r\n /** 清空所有 sprite(销毁实例) */\r\n clear(handlers: { onRemove: (sprite: VisibleSprite) => void }): void {\r\n for (const [id, sprite] of this.sprites) {\r\n handlers.onRemove(sprite)\r\n this.registry.destroyInstance(id)\r\n }\r\n this.sprites.clear()\r\n this.lastClips.clear()\r\n }\r\n\r\n stats(): number {\r\n return this.sprites.size\r\n }\r\n}\r\n","/**\r\n * AVCanvas 的封装层(feature 与 WebAV 之间的唯一桥梁)。\r\n *\r\n * 目的:把 WebAV 的实例管理、sprite 同步、事件订阅全部收拢到 core,\r\n * 使 features / hooks 不直接依赖 `@webav/*`(分层规则要求)。\r\n *\r\n * 关键设计:\r\n * - 本类 **不** 调用 `av.play()`。AVCanvas 自带 33ms 步进与自己的 AudioContext,\r\n * 与我们的 PlaybackClock 会打架;播放统一由外层 `previewFrame` 驱动。\r\n * - 播放时画面靠「每帧 preFrame + 重绘」推进;AVCanvas 的内部渲染循环在暂停态\r\n * 仍会按当前时间持续重绘,这正是我们要的(属性变化立刻反映)。\r\n * - **不使用 AVCanvas 内建的选中框与拖拽**。内建方案有三个硬伤:\r\n * 1. 选框样式只能靠 `!important` 覆盖它的私有 DOM 结构,脆弱;\r\n * 2. 每次 `propsChange` 都要读 `getBoundingClientRect()` 并写 style,拖动时与\r\n * React 重渲染抢主线程(实测单次拖动 6 个 ~100ms 的 long task);\r\n * 3. 拖动只改 sprite、不回写 store,必须额外搭一条回写链路才能持久化。\r\n * 因此把 sprite 设为 `interactable = 'disabled'`:它不参与内建选框与拖拽,\r\n * **但命中测试仍然有效**(AVCanvas 的 `activeSprite` setter 会拒绝 disabled,\r\n * 而命中测试走 `rect.checkHit`,不受影响)。feature 层据此用 DOM 自绘选框。\r\n * - `destroy()` 会移除全部 sprite 并释放实例。\r\n */\r\n\r\nimport { AVCanvas } from '@webav/av-canvas'\r\nimport type { VisibleSprite } from '@webav/av-cliper'\r\nimport type { ClipTransform, MediaAsset, Project } from '../../types'\r\nimport { pointInTransform } from '../../timeline/geometry'\r\nimport { SpriteSynchronizer } from './sprite-sync'\r\nimport type { ClipRegistry } from './clip-registry'\r\n\r\nexport interface PreviewCanvasOptions {\r\n width: number\r\n height: number\r\n bgColor?: string\r\n /** clip 实例注册表;每个编辑器实例一份,不能共享 */\r\n registry: ClipRegistry\r\n}\r\n\r\nexport interface SyncReport {\r\n created: string[]\r\n removed: string[]\r\n errors: Array<{ clipId: string; message: string }>\r\n}\r\n\r\nexport class PreviewCanvas {\r\n private canvas: AVCanvas\r\n private host: HTMLElement\r\n private synchronizer: SpriteSynchronizer\r\n private clipToSprite = new Map<string, VisibleSprite>()\r\n private destroyed = false\r\n private lastPreviewUs = -1\r\n /** 工程分辨率(命中测试的坐标系) */\r\n private projectWidth: number\r\n private projectHeight: number\r\n\r\n constructor(host: HTMLElement, opts: PreviewCanvasOptions) {\r\n this.host = host\r\n this.projectWidth = opts.width\r\n this.projectHeight = opts.height\r\n this.synchronizer = new SpriteSynchronizer(opts.registry)\r\n this.canvas = new AVCanvas(host, {\r\n width: opts.width,\r\n height: opts.height,\r\n bgColor: opts.bgColor ?? '#000000',\r\n })\r\n }\r\n\r\n get root(): AVCanvas {\r\n return this.canvas\r\n }\r\n\r\n /** 画布 element 的 CSS 尺寸与工程分辨率之比,供外层换算指针坐标 */\r\n getCssScale(): { x: number; y: number } {\r\n const rect = this.host.getBoundingClientRect()\r\n return {\r\n x: this.projectWidth > 0 && rect.width > 0 ? rect.width / this.projectWidth : 1,\r\n y: this.projectHeight > 0 && rect.height > 0 ? rect.height / this.projectHeight : 1,\r\n }\r\n }\r\n\r\n /** 与 store 数据对齐 sprite。\r\n *\r\n * @returns 变更与错误清单(单个 clip 失败不应中断整体同步)\r\n */\r\n async sync(project: Project, assets: MediaAsset[]): Promise<SyncReport> {\r\n if (this.destroyed) return { created: [], removed: [], errors: [] }\r\n this.projectWidth = project.width\r\n this.projectHeight = project.height\r\n const result = await this.synchronizer.sync(project, assets, {\r\n onAdd: async (sprite) => {\r\n await this.canvas.addSprite(sprite)\r\n },\r\n onRemove: (sprite) => {\r\n this.canvas.removeSprite(sprite)\r\n },\r\n })\r\n this.clipToSprite.clear()\r\n for (const clip of project.clips) {\r\n const sprite = this.synchronizer.getSprite(clip.id)\r\n if (sprite == null) continue\r\n this.clipToSprite.set(clip.id, sprite)\r\n }\r\n return { created: result.created, removed: result.removed, errors: result.errors }\r\n }\r\n\r\n /**\r\n * 把 store 里的「纯属性」(位置/尺寸/旋转/透明度/翻转)写回已有 sprite。\r\n *\r\n * 不重建 IClip,只改 sprite 字段 —— 属性面板拖动时高频调用,必须便宜。\r\n * sprite 的 rect 是普通对象(只在 setter 里比对新旧值),因此重复写入同值\r\n * 不会产生任何额外开销,也不需要再临时断开事件(本类已不再监听 propsChange)。\r\n */\r\n applyProps(project: Project): void {\r\n if (this.destroyed) return\r\n for (const clip of project.clips) {\r\n const sprite = this.clipToSprite.get(clip.id) ?? this.synchronizer.getSprite(clip.id)\r\n if (sprite == null) continue\r\n sprite.rect.x = clip.transform.x\r\n sprite.rect.y = clip.transform.y\r\n sprite.rect.w = clip.transform.w\r\n sprite.rect.h = clip.transform.h\r\n sprite.rect.angle = (clip.transform.angle * Math.PI) / 180\r\n sprite.opacity = clip.opacity\r\n sprite.flip = clip.flip\r\n }\r\n }\r\n\r\n /**\r\n * 只把「单个 clip」的几何写到 sprite,并立即重绘当前帧。\r\n *\r\n * 用于画布内拖动/缩放的实时预览:拖动过程中不写 store(避免 React 树与\r\n * 历史快照跟着每帧重建),只在这里直接改 sprite,松手时再提交一次。\r\n */\r\n async previewTransform(clipId: string, transform: ClipTransform): Promise<void> {\r\n if (this.destroyed) return\r\n const sprite = this.clipToSprite.get(clipId) ?? this.synchronizer.getSprite(clipId)\r\n if (sprite == null) return\r\n sprite.rect.x = transform.x\r\n sprite.rect.y = transform.y\r\n sprite.rect.w = transform.w\r\n sprite.rect.h = transform.h\r\n sprite.rect.angle = (transform.angle * Math.PI) / 180\r\n await this.redrawCurrentFrame()\r\n }\r\n\r\n /**\r\n * 渲染某一时刻。\r\n *\r\n * 同一个时间点重复调用会被跳过(AVCanvas.previewFrame 会完整走一遍 preFrame,\r\n * 每帧调用代价很高)。播放循环里时间必然递增,因此不会误伤。\r\n */\r\n async previewFrame(us: number): Promise<void> {\r\n if (this.destroyed) return\r\n const time = Math.max(0, Math.round(us))\r\n if (time === this.lastPreviewUs) return\r\n this.lastPreviewUs = time\r\n await this.canvas.previewFrame(time)\r\n }\r\n\r\n /** 强制重绘当前时刻(属性变化后调用) */\r\n async refresh(us: number): Promise<void> {\r\n this.lastPreviewUs = -1\r\n await this.previewFrame(us)\r\n }\r\n\r\n /** 重绘「上一次渲染的时间点」,用于画布内交互的即时反馈 */\r\n private async redrawCurrentFrame(): Promise<void> {\r\n const us = this.lastPreviewUs < 0 ? 0 : this.lastPreviewUs\r\n this.lastPreviewUs = -1\r\n await this.previewFrame(us)\r\n }\r\n\r\n /** 当前帧截图(base64 data URL) */\r\n captureImage(): string | null {\r\n if (this.destroyed) return null\r\n try {\r\n return this.canvas.captureImage()\r\n } catch {\r\n return null\r\n }\r\n }\r\n\r\n /**\r\n * 命中测试:返回最上层的可交互 clip。\r\n *\r\n * 为什么自己算而不直接用 AVCanvas 的 `activeSpriteByCoord`:\r\n * 那边只返回一个 sprite,调用方拿不到几何信息;且它按 `getSprites()` 的数组\r\n * 反序取第一个命中,语义与「轨道顺序即 zIndex」一致 —— 这里保持同样的顺序,\r\n * 但用 `clip.transform` 做旋转矩形命中,避免依赖 sprite 的内部状态。\r\n *\r\n * ⚠️ **必须校验播放头是否落在片段区间内**。\r\n * `sprite.visible` 只反映「轨道是否被隐藏」(sync 时由 `track.hidden` 决定),\r\n * 与时间无关;AVCanvas 的时间可见性只影响绘制,不会让 sprite 不可命中。\r\n * 若这里不判时间,播放头不在该片段上时(画面里根本看不到它),用户仍然能\r\n * 在它原来的位置点中并拖动它 —— 看起来像「凭空拖动了一块空白区域」。\r\n *\r\n * @param point 工程坐标\r\n * @param project 用于取 zIndex 顺序\r\n * @param atUs 当前时间(播放头),判断哪些 clip 此刻真实可见\r\n */\r\n hitTest(point: { x: number; y: number }, project: Project, atUs: number): string | null {\r\n if (this.destroyed) return null\r\n // 轨道顺序即层级:数组越靠后越在上层\r\n const order = new Map(project.tracks.map((track, index) => [track.id, index]))\r\n const candidates = project.clips\r\n .filter((clip) => {\r\n const sprite = this.clipToSprite.get(clip.id)\r\n if (sprite == null || !sprite.visible) return false\r\n // 纯音频 clip 的 transform 是「铺满画布」的占位值,但 sprite 尺寸为 0:\r\n // 若用 transform 判定,点画布任意位置都会选中音频片段。\r\n if (sprite.rect.w <= 0 || sprite.rect.h <= 0) return false\r\n // 只有播放头落在 [start, end] 内的片段此刻才在画面上\r\n if (atUs < clip.startUs || atUs > clip.startUs + clip.durationUs) return false\r\n return pointInTransform(point, clip.transform)\r\n })\r\n .sort((a, b) => {\r\n const za = order.get(a.trackId) ?? 0\r\n const zb = order.get(b.trackId) ?? 0\r\n if (za !== zb) return za - zb\r\n return a.startUs - b.startUs\r\n })\r\n const top = candidates[candidates.length - 1]\r\n return top?.id ?? null\r\n }\r\n\r\n /** 取某 clip 当前 sprite 上的实际几何(画布内拖动开始时作为快照) */\r\n getSpriteTransform(clipId: string): ClipTransform | null {\r\n const sprite = this.clipToSprite.get(clipId) ?? this.synchronizer.getSprite(clipId)\r\n if (sprite == null) return null\r\n const rect = sprite.rect\r\n return {\r\n x: rect.x,\r\n y: rect.y,\r\n w: rect.w,\r\n h: rect.h,\r\n angle: (rect.angle * 180) / Math.PI,\r\n }\r\n }\r\n\r\n /** 合成媒体流(录制 Lab 用) */\r\n captureStream(): MediaStream {\r\n return this.canvas.captureStream()\r\n }\r\n\r\n destroy(): void {\r\n if (this.destroyed) return\r\n this.destroyed = true\r\n this.clipToSprite.clear()\r\n this.synchronizer.clear({\r\n onRemove: (sprite) => {\r\n try {\r\n this.canvas.removeSprite(sprite)\r\n } catch {\r\n // ignore\r\n }\r\n },\r\n })\r\n try {\r\n this.canvas.destroy()\r\n } catch {\r\n // ignore\r\n }\r\n }\r\n}\r\n","/**\n * 修正 WebAV 输出的 fMP4 时长。\n *\n * WebAV 输出的是分片 MP4(moof/mdat),部分播放器无法从 moov 中读出总时长。\n * `fixFMP4Duration` 会重写 moov 的 duration 字段。\n */\n\nimport { fixFMP4Duration } from '@webav/av-cliper'\n\n/**\n * @param blob 原始 MP4\n * @returns 修正后的 MP4(同一 MIME)\n */\nexport async function fixFMP4DurationStream(blob: Blob): Promise<Blob> {\n const stream = blob.stream() as ReadableStream<Uint8Array>\n const fixed = await fixFMP4Duration(stream)\n return new Blob([await new Response(fixed).arrayBuffer()], { type: 'video/mp4' })\n}\n","/**\n * 自定义 IClip:把已解码好的 PCM 数据当作音频素材使用。\n *\n * 为什么需要它:实测 `@webav/av-cliper@1.2.8` 的 `AudioClip` 构造函数无论传流\n * 还是传 `Float32Array[]`,内部都会执行 `new AudioContext({ sampleRate: 48000 })`,\n * 而 Web Worker 里既没有 `AudioContext` 也没有 `OfflineAudioContext` —— 因此\n * **AudioClip 无法在 Worker 中构造**(导出 Worker 会直接抛 \"AudioContext is not defined\")。\n *\n * 取舍:主线程用官方 `AudioClip` 解码一次(保证解码/重采样由 WebAV 负责),\n * 把 48kHz 双声道 PCM 传进 Worker,Worker 用本类提供数据。\n *\n * tick 语义严格对齐 `AudioClip.tick`:返回「距上次 tick 的增量」;\n * 时间倒退或跨度 > 3s 时重置游标(与 WebAV 文档一致)。\n */\n\nimport type { IClip } from '@webav/av-cliper'\nimport type { ClipMetaBase } from './meta'\n\nexport interface PcmClipOptions {\n /** 采样率,默认 48000 */\n sampleRate?: number\n}\n\nexport class PcmClip implements IClip {\n readonly ready: Promise<ClipMetaBase>\n private channels: Float32Array[]\n private sampleRate: number\n private durationUs: number\n /** 上次 tick 的源时间(微秒) */\n private lastUs = 0\n /** 上次取到的采样帧位置 */\n private cursor = 0\n private destroyed = false\n\n constructor(pcm: Float32Array[], opts: PcmClipOptions = {}) {\n this.channels = pcm.length > 0 ? pcm : [new Float32Array(0)]\n this.sampleRate = opts.sampleRate ?? 48_000\n const frames = this.channels[0]?.length ?? 0\n this.durationUs = Math.round((frames / this.sampleRate) * 1_000_000)\n this.ready = Promise.resolve(this.meta)\n }\n\n get meta(): ClipMetaBase {\n // 音频没有画面,宽高为 0(与 AudioClip 一致)\n return { width: 0, height: 0, duration: this.durationUs }\n }\n\n async tick(time: number): Promise<{\n audio?: Float32Array[]\n state: 'done' | 'success'\n }> {\n if (this.destroyed) return { audio: [], state: 'done' }\n if (time >= this.durationUs) return { audio: [], state: 'done' }\n\n const deltaUs = time - this.lastUs\n // 与 AudioClip 一致:倒退或跨度超过 3s 时重置游标(音频会短暂中断)\n if (time < this.lastUs || deltaUs > 3_000_000) {\n this.lastUs = time\n this.cursor = Math.ceil((time / 1_000_000) * this.sampleRate)\n return { audio: [new Float32Array(0)], state: 'success' }\n }\n\n this.lastUs = time\n const frames = Math.ceil((deltaUs / 1_000_000) * this.sampleRate)\n const nextCursor = this.cursor + frames\n const slices = this.channels.map((channel) => channel.slice(this.cursor, nextCursor))\n this.cursor = nextCursor\n return { audio: slices, state: 'success' }\n }\n\n async clone(): Promise<this> {\n // 共享同一份 PCM(只读),但游标独立\n const copy = new PcmClip(this.channels, { sampleRate: this.sampleRate })\n return copy as this\n }\n\n async split(time: number): Promise<[this, this]> {\n if (time <= 0 || time >= this.durationUs) throw new Error('\"time\" out of bounds')\n const frame = Math.floor((time / 1_000_000) * this.sampleRate)\n const left = new PcmClip(\n this.channels.map((c) => c.slice(0, frame)),\n { sampleRate: this.sampleRate },\n )\n const right = new PcmClip(\n this.channels.map((c) => c.slice(frame)),\n { sampleRate: this.sampleRate },\n )\n await Promise.all([left.ready, right.ready])\n return [left as this, right as this]\n }\n\n destroy(): void {\n this.destroyed = true\n this.channels = []\n }\n}\n","/**\r\n * 导出 Worker 的运行时逻辑(WebAV 调用集中在此)。\r\n *\r\n * `features/export/export.worker.ts` 只是薄入口,避免在 features 层直接 import WebAV。\r\n *\r\n * 为什么必须放 Worker:`Combinator.output()` 在当前线程同步步进编码,\r\n * 主线程执行会冻结 UI(30s 1080p 通常需要数十秒)。\r\n *\r\n * 已知限制(实测):\r\n * - 文字素材依赖 `document`(renderTxt2ImgBitmap 内部 createElement + SVG foreignObject),\r\n * Worker 内无法重建。调用方在检测到文字片段时应回退到主线程导出;\r\n * - 音频素材的 `AudioClip` 构造依赖 `AudioContext.decodeAudioData`,Worker 内不存在该 API,\r\n * 因此必须由主线程预解码 PCM 后传入(见 WorkerAssetPayload.pcm);\r\n * - `MediaStreamClip` 依赖实时时间流,不能放进 Combinator,任何线程都不支持。\r\n */\r\n\r\nimport {\r\n Combinator,\r\n EmbedSubtitlesClip,\r\n ImgClip,\r\n MP4Clip,\r\n OffscreenSprite,\r\n fixFMP4Duration,\r\n} from '@webav/av-cliper'\r\nimport type { IClip } from '@webav/av-cliper'\r\nimport type {\r\n WorkerAssetPayload,\r\n WorkerRequest,\r\n WorkerResponse,\r\n WorkerTimelinePayload,\r\n} from './types'\r\nimport { GainClip } from './gain-clip'\r\nimport { PcmClip } from './pcm-clip'\r\nimport { OffsetClip } from './offset-clip'\r\nimport { applyOpacityFade } from './sprite-factory'\r\nimport { mapTransformToOutput } from './coordinate'\r\nimport type { TimelineClip } from '../../types'\r\n\r\n/** 文字素材需要 DOM,Worker 内无法重建;调用方应在派发前过滤 */\r\nexport class WorkerUnsupportedClipError extends Error {\r\n constructor(message: string) {\r\n super(message)\r\n this.name = 'WorkerUnsupportedClipError'\r\n }\r\n}\r\n\r\nlet cancelled = false\r\n\r\nfunction toStream(blob: Blob): ReadableStream<Uint8Array> {\r\n return blob.stream() as ReadableStream<Uint8Array>\r\n}\r\n\r\n/** 轨道静音:音量置 0(画面不受影响) */\r\nfunction withSilence(clip: IClip): IClip {\r\n return new GainClip(clip, {\r\n volume: 0,\r\n fadeInUs: 0,\r\n fadeOutUs: 0,\r\n durationUs: Number.MAX_SAFE_INTEGER,\r\n playbackRate: 1,\r\n })\r\n}\r\n\r\n/** 音量 / 淡入淡出包装(与主线程 exporter.withClipGain 保持一致的语义) */\r\nfunction withGain(clip: IClip, timeline: TimelineClip): IClip {\r\n if (timeline.volume === 1 && timeline.fadeInUs === 0 && timeline.fadeOutUs === 0) return clip\r\n return new GainClip(clip, {\r\n volume: timeline.volume,\r\n fadeInUs: timeline.fadeInUs,\r\n fadeOutUs: timeline.fadeOutUs,\r\n durationUs: timeline.durationUs,\r\n playbackRate: timeline.playbackRate,\r\n })\r\n}\r\n\r\n/** Worker 内按素材类型重建 IClip(与主线程 clip-factory 对齐,但不含 DOM 依赖的类型) */\r\nexport async function buildClipInWorker(\r\n asset: WorkerAssetPayload,\r\n): Promise<IClip> {\r\n switch (asset.kind) {\r\n case 'video': {\r\n if (asset.file == null) throw new Error(`素材文件缺失:${asset.id}`)\r\n const clip = new MP4Clip(toStream(asset.file))\r\n await clip.ready\r\n return clip\r\n }\r\n case 'audio': {\r\n // ⚠️ 实测:`AudioClip` 构造函数(无论传流还是 Float32Array[])内部都会\r\n // `new AudioContext(...)`,Worker 中没有该 API,必然抛 \"AudioContext is not defined\"。\r\n // 因此主线程预解码 PCM,Worker 侧用不依赖 AudioContext 的 PcmClip 提供数据。\r\n if (asset.pcm == null || asset.pcm.length === 0) {\r\n throw new Error(`音频素材缺少 PCM 数据:${asset.id}`)\r\n }\r\n const clip = new PcmClip(asset.pcm)\r\n await clip.ready\r\n return clip\r\n }\r\n case 'image': {\r\n if (asset.file == null) throw new Error(`图片文件缺失:${asset.id}`)\r\n const clip = new ImgClip(toStream(asset.file))\r\n await clip.ready\r\n return clip\r\n }\r\n case 'subtitle': {\r\n if (!asset.srt) throw new Error('字幕内容缺失')\r\n const clip = new EmbedSubtitlesClip(asset.srt, {\r\n videoWidth: asset.width,\r\n videoHeight: asset.height,\r\n color: asset.subtitleStyle?.color,\r\n fontFamily: asset.subtitleStyle?.fontFamily,\r\n fontSize: asset.subtitleStyle?.fontSize,\r\n strokeStyle: asset.subtitleStyle?.strokeStyle,\r\n lineWidth: asset.subtitleStyle?.lineWidth,\r\n bottomOffset: asset.subtitleStyle?.bottomOffset,\r\n textBgColor: asset.subtitleStyle?.textBgColor,\r\n })\r\n await clip.ready\r\n return clip\r\n }\r\n case 'text':\r\n throw new WorkerUnsupportedClipError(\r\n '文字素材依赖 DOM 渲染,无法在 Worker 内重建(请使用主线程导出)',\r\n )\r\n default:\r\n throw new Error(`未知素材类型:${String(asset.kind)}`)\r\n }\r\n}\r\n\r\nexport interface WorkerExportResult {\r\n buffer: ArrayBuffer\r\n skipped: Array<{ clipId: string; reason: string }>\r\n}\r\n\r\n/**\r\n * 在 Worker 内执行完整导出。\r\n *\r\n * @param assets 素材快照(File 通过结构化克隆传入,不复制二进制)\r\n * @param timeline 时间轴纯数据\r\n * @param onProgress 0~1 进度\r\n * @param shouldCancel 轮询式取消检查\r\n */\r\nexport async function runExportInWorker(\r\n assets: WorkerAssetPayload[],\r\n timeline: WorkerTimelinePayload,\r\n onProgress: (progress: number) => void,\r\n shouldCancel: () => boolean,\r\n): Promise<WorkerExportResult> {\r\n const assetMap = new Map(assets.map((a) => [a.id, a]))\r\n const trackMap = new Map((timeline.tracks ?? []).map((t) => [t.id, t]))\r\n const combinator = new Combinator({\r\n width: timeline.width,\r\n height: timeline.height,\r\n fps: timeline.fps,\r\n bitrate: timeline.bitrate,\r\n bgColor: timeline.bgColor,\r\n audio: timeline.includeAudio ? undefined : false,\r\n })\r\n\r\n // 按素材缓存「母版」,每个时间轴 clip 从中 clone 出独立实例(各自带游标)。\r\n // 复用同一个实例会让多个 clip 共享 tick 游标,导出画面会错位 —— 预览侧\r\n // (ClipRegistry.createInstance)也是「每个 clip 一份实例」,两边必须一致。\r\n const masterCache = new Map<string, IClip>()\r\n const derived: IClip[] = []\r\n const skipped: Array<{ clipId: string; reason: string }> = []\r\n // 工程坐标(clip.transform 的基准)与输出坐标\r\n const sourceSize = {\r\n width: timeline.projectWidth ?? timeline.width,\r\n height: timeline.projectHeight ?? timeline.height,\r\n }\r\n const targetSize = { width: timeline.width, height: timeline.height }\r\n let maxTimeUs = 0\r\n let zIndex = 0\r\n\r\n const abortIfNeeded = () => {\r\n if (shouldCancel()) throw new Error('__cancelled__')\r\n }\r\n\r\n try {\r\n const ordered = [...timeline.clips].sort((a, b) => {\r\n const aAudio = a.kind === 'audio' ? 1 : 0\r\n const bAudio = b.kind === 'audio' ? 1 : 0\r\n if (aAudio !== bAudio) return aAudio - bAudio\r\n return a.clip.startUs - b.clip.startUs\r\n })\r\n\r\n for (const item of ordered) {\r\n abortIfNeeded()\r\n const asset = assetMap.get(item.clip.assetId)\r\n if (!asset) {\r\n skipped.push({ clipId: item.clip.id, reason: '素材缺失' })\r\n continue\r\n }\r\n // 轨道隐藏 → 整条轨道不参与合成(与预览一致)\r\n const track = trackMap.get(item.clip.trackId)\r\n if (track?.hidden === true) continue\r\n // 片段静音 → 完全不参与合成(与预览「静音不绘制」一致)\r\n if (item.clip.muted) continue\r\n // 包含音频轨但本次导出不含音轨 → 整个片段跳过\r\n if (item.kind === 'audio' && !timeline.includeAudio) continue\r\n\r\n let master = masterCache.get(asset.id)\r\n if (master == null) {\r\n try {\r\n master = await buildClipInWorker(asset)\r\n masterCache.set(asset.id, master)\r\n } catch (err) {\r\n const reason = err instanceof Error ? err.message : String(err)\r\n console.warn('[export.worker] clip skipped:', item.clip.id, reason)\r\n skipped.push({ clipId: item.clip.id, reason })\r\n continue\r\n }\r\n }\r\n\r\n const tc: TimelineClip = item.clip\r\n let instance: IClip\r\n try {\r\n instance = await master.clone()\r\n } catch (err) {\r\n const reason = err instanceof Error ? err.message : String(err)\r\n skipped.push({ clipId: tc.id, reason })\r\n continue\r\n }\r\n // 入点裁剪:与预览共用 OffsetClip(sprite.tick 永远从 0 开始取数据)\r\n if (tc.inPointUs > 0) {\r\n instance = new OffsetClip(instance, {\r\n inPointUs: tc.inPointUs,\r\n durationUs: tc.durationUs * tc.playbackRate,\r\n })\r\n }\r\n derived.push(instance)\r\n\r\n // 轨道静音:只丢音频,画面照常(与预览「静音轨道仍有画面」一致)。\r\n // GainClip 在 muted 时直接返回静音数据,因此复用同一层包络即可。\r\n const gainInput = track?.muted === true ? withSilence(instance) : instance\r\n const sprite = new OffscreenSprite(withGain(gainInput, tc))\r\n // 工程坐标 → 输出坐标(导出分辨率可不同于工程分辨率,不换算会挤在左上角)\r\n const mapped = mapTransformToOutput(tc.transform, sourceSize, targetSize)\r\n sprite.rect.x = mapped.x\r\n sprite.rect.y = mapped.y\r\n sprite.rect.w = mapped.w\r\n sprite.rect.h = mapped.h\r\n sprite.rect.angle = (mapped.angle * Math.PI) / 180\r\n sprite.opacity = tc.opacity\r\n sprite.flip = tc.flip\r\n sprite.zIndex = zIndex\r\n sprite.time = {\r\n offset: tc.startUs,\r\n duration: tc.durationUs,\r\n playbackRate: tc.playbackRate,\r\n }\r\n // 淡入淡出是画面属性,Worker 与主线程必须一致(否则预览能看到淡入,\r\n // 导出却直接满亮度 —— 典型的「预览与导出不相符」)\r\n applyOpacityFade(sprite, tc)\r\n zIndex += 1\r\n maxTimeUs = Math.max(maxTimeUs, tc.startUs + tc.durationUs)\r\n\r\n await combinator.addSprite(sprite, { main: false })\r\n }\r\n\r\n if (maxTimeUs <= 0) throw new Error('时间轴为空,无法导出')\r\n\r\n combinator.on('OutputProgress', (progress) => onProgress(progress))\r\n\r\n const stream = combinator.output({ maxTime: timeline.maxTimeUs ?? maxTimeUs })\r\n const raw = await new Response(stream).arrayBuffer()\r\n abortIfNeeded()\r\n\r\n let buffer = raw\r\n try {\r\n const fixed = await fixFMP4Duration(\r\n new Response(raw).body as ReadableStream<Uint8Array>,\r\n )\r\n buffer = await new Response(fixed).arrayBuffer()\r\n } catch (err) {\r\n console.warn('[export-worker] fixFMP4Duration failed, fallback to raw', err)\r\n }\r\n\r\n return { buffer, skipped }\r\n } finally {\r\n for (const clip of derived) {\r\n try {\r\n clip.destroy()\r\n } catch {\r\n // ignore\r\n }\r\n }\r\n derived.length = 0\r\n for (const clip of masterCache.values()) {\r\n try {\r\n clip.destroy()\r\n } catch {\r\n // ignore\r\n }\r\n }\r\n masterCache.clear()\r\n try {\r\n combinator.destroy()\r\n } catch {\r\n // ignore\r\n }\r\n }\r\n}\r\n\r\n/** Worker 消息入口:由薄入口文件绑定到 self.onmessage */\r\nexport function handleWorkerMessage(event: MessageEvent<WorkerRequest>): void {\r\n const post = (message: WorkerResponse, transfer?: Transferable[]) => {\r\n const scope = self as unknown as { postMessage: (m: WorkerResponse, t?: Transferable[]) => void }\r\n scope.postMessage(message, transfer ?? [])\r\n }\r\n\r\n const message = event.data\r\n if (message.type === 'cancel') {\r\n cancelled = true\r\n return\r\n }\r\n if (message.type !== 'export') return\r\n\r\n cancelled = false\r\n runExportInWorker(\r\n message.assets,\r\n message.timeline,\r\n (progress) => {\r\n if (!cancelled) post({ type: 'progress', progress })\r\n },\r\n () => cancelled,\r\n )\r\n .then(({ buffer, skipped }) => {\r\n post({ type: 'done', buffer, elapsedMs: 0, skipped }, [buffer])\r\n })\r\n .catch((err: unknown) => {\r\n const msg = err instanceof Error ? err.message : String(err)\r\n post({ type: 'error', message: msg === '__cancelled__' ? '导出已取消' : msg })\r\n })\r\n}\r\n"],"mappings":"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"}
|