@pneuma-craft/video 0.4.0 → 0.5.0
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/CHANGELOG.md +30 -0
- package/dist/index.cjs +55 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +55 -27
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c7a09ae: Add **PreviewFrame**, a planning-layer visual attached to a track at a single time point that lets go (does not render) when a real clip on the same track covers that moment. Powers the AIGC progressive-fidelity workflow (cheap sketch → upgraded anchor → real clip) without diluting `Clip` semantics.
|
|
8
|
+
|
|
9
|
+
`@pneuma-craft/timeline` (additive)
|
|
10
|
+
|
|
11
|
+
- New types: `PreviewFrame`, `ResolvedPreviewFrame`, `ResolvedFrame.previewFrames[]`
|
|
12
|
+
- `Track` gains a sorted `previewFrames: PreviewFrame[]` array
|
|
13
|
+
- New commands (mirroring clip ops): `composition:add-preview-frame`, `remove-preview-frame`, `move-preview-frame`, `rebind-preview-frame`
|
|
14
|
+
- Matching events with undo compensations
|
|
15
|
+
- `resolveFrame()` applies a per-track let-go rule (clip wins over preview at the same time on the same track)
|
|
16
|
+
- `recomputeDuration()` extends to the last preview-frame time
|
|
17
|
+
- New utility `buildSetPreviewFrameCommand()` for agent upsert ergonomics
|
|
18
|
+
- `composition:add-track` now accepts `previewFrames` as optional in its input shape (defaults to `[]`)
|
|
19
|
+
|
|
20
|
+
`@pneuma-craft/video` (additive + small internal signature)
|
|
21
|
+
|
|
22
|
+
- `createFrameRenderer`'s 5th argument now accepts `{ subtitleRenderer?, includePreviewFrames? }`. The pre-existing positional `SubtitleRenderer` form is detected and normalized — existing call sites continue to compile.
|
|
23
|
+
- `PlaybackEngineOptions.includePreviewFrames` (defaults to `true`) — playback shows planning visuals
|
|
24
|
+
- `ExportEngineOptions.includePreviewFrames` (defaults to `false`) — export = finished cut by default; opt-in for review-grade exports of unfinished timelines
|
|
25
|
+
|
|
26
|
+
See `docs/specs/2026-05-09-preview-frame-design.md` for the full design and `docs/recipes/preview-frames.md` for an integration guide.
|
|
27
|
+
|
|
28
|
+
### Patch Changes
|
|
29
|
+
|
|
30
|
+
- Updated dependencies [c7a09ae]
|
|
31
|
+
- @pneuma-craft/timeline@0.4.0
|
|
32
|
+
|
|
3
33
|
All notable changes to `@pneuma-craft/video` will be documented in this file.
|
|
4
34
|
|
|
5
35
|
## 0.4.0
|
package/dist/index.cjs
CHANGED
|
@@ -471,31 +471,58 @@ async function createCompositor(width, height, type = "auto") {
|
|
|
471
471
|
|
|
472
472
|
// src/frame-renderer.ts
|
|
473
473
|
var import_timeline = require("@pneuma-craft/timeline");
|
|
474
|
-
function
|
|
474
|
+
function normalizeOptions(arg) {
|
|
475
|
+
if (arg === void 0) return {};
|
|
476
|
+
if (typeof arg === "function") return { subtitleRenderer: arg };
|
|
477
|
+
return arg;
|
|
478
|
+
}
|
|
479
|
+
function createFrameRenderer(decoder, compositor, width, height, optionsOrLegacySubtitleRenderer) {
|
|
480
|
+
const options = normalizeOptions(optionsOrLegacySubtitleRenderer);
|
|
481
|
+
const { subtitleRenderer, includePreviewFrames = false } = options;
|
|
475
482
|
return {
|
|
476
483
|
async renderFrame(composition, time) {
|
|
477
484
|
const resolved = (0, import_timeline.resolveFrame)(composition, time);
|
|
478
|
-
const
|
|
479
|
-
const
|
|
485
|
+
const clipsByTrack = /* @__PURE__ */ new Map();
|
|
486
|
+
for (const rc of resolved.clips) {
|
|
487
|
+
const arr = clipsByTrack.get(rc.track.id);
|
|
488
|
+
if (arr) arr.push(rc);
|
|
489
|
+
else clipsByTrack.set(rc.track.id, [rc]);
|
|
490
|
+
}
|
|
491
|
+
const previewByTrack = /* @__PURE__ */ new Map();
|
|
492
|
+
for (const rpf of resolved.previewFrames) previewByTrack.set(rpf.track.id, rpf);
|
|
480
493
|
const layers = [];
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
+
let zIndex = 0;
|
|
495
|
+
for (const track of composition.tracks) {
|
|
496
|
+
if (track.type !== "video") continue;
|
|
497
|
+
const trackClips = clipsByTrack.get(track.id);
|
|
498
|
+
if (trackClips && trackClips.length > 0) {
|
|
499
|
+
for (const rc of trackClips) {
|
|
500
|
+
const source = await decoder.decodeVideoFrame(
|
|
501
|
+
rc.clip.assetId,
|
|
502
|
+
rc.localTime,
|
|
503
|
+
width,
|
|
504
|
+
height
|
|
505
|
+
);
|
|
506
|
+
layers.push({ source, opacity: 1, zIndex: zIndex++ });
|
|
507
|
+
}
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
if (includePreviewFrames) {
|
|
511
|
+
const rpf = previewByTrack.get(track.id);
|
|
512
|
+
if (rpf) {
|
|
513
|
+
const source = await decoder.decodeVideoFrame(
|
|
514
|
+
rpf.previewFrame.assetId,
|
|
515
|
+
0,
|
|
516
|
+
width,
|
|
517
|
+
height
|
|
518
|
+
);
|
|
519
|
+
layers.push({ source, opacity: 1, zIndex: zIndex++ });
|
|
520
|
+
}
|
|
521
|
+
}
|
|
494
522
|
}
|
|
495
|
-
if (subtitleRenderer
|
|
496
|
-
const
|
|
497
|
-
for (
|
|
498
|
-
const rc = subtitleClips[i];
|
|
523
|
+
if (subtitleRenderer) {
|
|
524
|
+
const subtitleClips = resolved.clips.filter((rc) => rc.track.type === "subtitle");
|
|
525
|
+
for (const rc of subtitleClips) {
|
|
499
526
|
const rendered = await subtitleRenderer({
|
|
500
527
|
clip: rc.clip,
|
|
501
528
|
localTime: rc.localTime,
|
|
@@ -503,11 +530,7 @@ function createFrameRenderer(decoder, compositor, width, height, subtitleRendere
|
|
|
503
530
|
height
|
|
504
531
|
});
|
|
505
532
|
if (rendered) {
|
|
506
|
-
layers.push({
|
|
507
|
-
source: rendered,
|
|
508
|
-
opacity: 1,
|
|
509
|
-
zIndex: baseZ + i
|
|
510
|
-
});
|
|
533
|
+
layers.push({ source: rendered, opacity: 1, zIndex: zIndex++ });
|
|
511
534
|
}
|
|
512
535
|
}
|
|
513
536
|
}
|
|
@@ -908,6 +931,7 @@ function createPlaybackEngine(options) {
|
|
|
908
931
|
const compositorType = options?.compositorType ?? "canvas2d";
|
|
909
932
|
const decodeTimeoutMs = options?.decodeTimeoutMs ?? DEFAULT_DECODE_TIMEOUT_MS;
|
|
910
933
|
const subtitleRenderer = options?.subtitleRenderer;
|
|
934
|
+
const includePreviewFrames = options?.includePreviewFrames ?? true;
|
|
911
935
|
let _state = "idle";
|
|
912
936
|
let _playbackRate = 1;
|
|
913
937
|
let _loop = null;
|
|
@@ -1076,7 +1100,7 @@ function createPlaybackEngine(options) {
|
|
|
1076
1100
|
_compositor,
|
|
1077
1101
|
composition.settings.width,
|
|
1078
1102
|
composition.settings.height,
|
|
1079
|
-
subtitleRenderer
|
|
1103
|
+
{ subtitleRenderer, includePreviewFrames }
|
|
1080
1104
|
);
|
|
1081
1105
|
_clock = createMasterClock({
|
|
1082
1106
|
audioContext: _audioContext,
|
|
@@ -1235,6 +1259,7 @@ function createOfflineAudioRenderer() {
|
|
|
1235
1259
|
var import_mediabunny2 = require("mediabunny");
|
|
1236
1260
|
function createExportEngine(options) {
|
|
1237
1261
|
const subtitleRenderer = options?.subtitleRenderer;
|
|
1262
|
+
const includePreviewFrames = options?.includePreviewFrames ?? false;
|
|
1238
1263
|
const progressListeners = /* @__PURE__ */ new Set();
|
|
1239
1264
|
let abortController = null;
|
|
1240
1265
|
function notifyProgress(progress) {
|
|
@@ -1257,7 +1282,10 @@ function createExportEngine(options) {
|
|
|
1257
1282
|
const decoderAudioCtx = new OfflineAudioContext(1, 1, composition.settings.sampleRate || 48e3);
|
|
1258
1283
|
const decoder = createMediaDecoder(resolver, decoderAudioCtx);
|
|
1259
1284
|
const compositor = await createCompositor(width, height, "canvas2d");
|
|
1260
|
-
const renderer = createFrameRenderer(decoder, compositor, width, height,
|
|
1285
|
+
const renderer = createFrameRenderer(decoder, compositor, width, height, {
|
|
1286
|
+
subtitleRenderer,
|
|
1287
|
+
includePreviewFrames
|
|
1288
|
+
});
|
|
1261
1289
|
try {
|
|
1262
1290
|
const format = options2.format === "webm" ? new import_mediabunny2.WebMOutputFormat() : new import_mediabunny2.Mp4OutputFormat({ fastStart: "in-memory" });
|
|
1263
1291
|
const target = new import_mediabunny2.BufferTarget();
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/media-decoder.ts","../src/canvas2d-compositor.ts","../src/gpu-compositor.ts","../src/compositor.ts","../src/frame-renderer.ts","../src/master-clock.ts","../src/audio-scheduler.ts","../src/playback-engine.ts","../src/offline-audio-renderer.ts","../src/export-engine.ts"],"sourcesContent":["// ── Types ──────────────────────────────────────────────────────────────\nexport type {\n AssetResolver,\n MediaInfo,\n MediaDecoder,\n CompositeLayer,\n Compositor,\n RenderedFrame,\n FrameRenderer,\n SubtitleRenderer,\n SubtitleRenderParams,\n ClockState,\n MasterClock,\n AudioScheduler,\n PlaybackState,\n PlaybackEngine,\n ExportOptions,\n ExportEngine,\n OfflineAudioRenderer,\n} from './types.js';\n\n// ── Creation Functions ─────────────────────────────────────────────────\nexport { createPlaybackEngine } from './playback-engine.js';\nexport type { PlaybackEngineOptions } from './playback-engine.js';\nexport { createExportEngine } from './export-engine.js';\nexport type { ExportEngineOptions } from './export-engine.js';\nexport { createFrameRenderer } from './frame-renderer.js';\nexport { createMasterClock } from './master-clock.js';\nexport type { MasterClockOptions } from './master-clock.js';\nexport { createAudioScheduler } from './audio-scheduler.js';\nexport type { AudioSchedulerOptions } from './audio-scheduler.js';\nexport { createMediaDecoder } from './media-decoder.js';\n\n// ── Compositor ─────────────────────────────────────────────────────────\nexport { createCompositor } from './compositor.js';\nexport type { CompositorType } from './compositor.js';\nexport { createCanvas2DCompositor } from './canvas2d-compositor.js';\nexport { createGPUCompositor } from './gpu-compositor.js';\n\n// ── Offline Audio ──────────────────────────────────────────────────────\nexport { createOfflineAudioRenderer } from './offline-audio-renderer.js';\n","import type { MediaDecoder, AssetResolver, MediaInfo } from './types.js';\nimport { Input, BlobSource, CanvasSink, AudioBufferSink, ALL_FORMATS } from 'mediabunny';\n\ninterface CachedAsset {\n input: InstanceType<typeof Input>;\n blob: Blob;\n videoSink: InstanceType<typeof CanvasSink> | null;\n imageBitmap: ImageBitmap | null;\n audioBuffer: AudioBuffer | null;\n mediaInfo: MediaInfo | null;\n}\n\n/**\n * Decodes an image blob and rasterizes it onto a (width × height) OffscreenCanvas\n * with \"contain\" fit (aspect-preserving scale, transparent letterbox). This matches\n * the CanvasSink's `fit: 'contain'` + `alpha: true` behavior so image layers and\n * video layers share the exact same output dimensions — important because the GPU\n * compositor copies external images into a texture of that exact size and rejects\n * mismatches. The `{ alpha: true }` context option preserves the source image's\n * alpha channel (e.g. a PNG with transparency) and leaves letterbox regions fully\n * transparent so underlying layers show through.\n */\nasync function decodeImageFitted(blob: Blob, width: number, height: number): Promise<ImageBitmap> {\n const raw = await createImageBitmap(blob);\n try {\n const canvas = new OffscreenCanvas(width, height);\n const ctx = canvas.getContext('2d', { alpha: true });\n if (!ctx) throw new Error('Failed to get 2d context for image resize');\n const scale = Math.min(width / raw.width, height / raw.height);\n const drawW = raw.width * scale;\n const drawH = raw.height * scale;\n const dx = (width - drawW) / 2;\n const dy = (height - drawH) / 2;\n ctx.drawImage(raw, 0, 0, raw.width, raw.height, dx, dy, drawW, drawH);\n return await createImageBitmap(canvas);\n } finally {\n raw.close();\n }\n}\n\nexport function createMediaDecoder(\n resolver: AssetResolver,\n audioContext: BaseAudioContext,\n): MediaDecoder {\n const cache = new Map<string, CachedAsset>();\n const initPromises = new Map<string, Promise<CachedAsset>>();\n\n async function getOrCreateAsset(assetId: string): Promise<CachedAsset> {\n const existing = cache.get(assetId);\n if (existing) return existing;\n\n const pending = initPromises.get(assetId);\n if (pending) return pending;\n\n const promise = (async () => {\n const blob = await resolver.fetchBlob(assetId);\n const input = new Input({ source: new BlobSource(blob), formats: ALL_FORMATS });\n const asset: CachedAsset = { input, blob, videoSink: null, imageBitmap: null, audioBuffer: null, mediaInfo: null };\n cache.set(assetId, asset);\n initPromises.delete(assetId);\n return asset;\n })();\n\n // Remove from initPromises on failure so future attempts can retry\n promise.catch(() => {\n initPromises.delete(assetId);\n });\n\n initPromises.set(assetId, promise);\n return promise;\n }\n\n return {\n async decodeVideoFrame(assetId, time, width, height) {\n const asset = await getOrCreateAsset(assetId);\n\n // Image fast path — cached ImageBitmap (already fitted to the target\n // dimensions), timestamp ignored. An image clip behaves as a static\n // frame that occupies its Clip's full duration.\n if (asset.imageBitmap) return asset.imageBitmap;\n\n if (!asset.videoSink) {\n let videoTrack: Awaited<ReturnType<typeof asset.input.getPrimaryVideoTrack>> | null = null;\n try {\n videoTrack = await asset.input.getPrimaryVideoTrack();\n } catch {\n // MediaBunny couldn't parse this blob as a media container — likely\n // a standalone image (png/jpg/webp/gif). Fall through to ImageBitmap.\n }\n if (!videoTrack) {\n try {\n asset.imageBitmap = await decodeImageFitted(asset.blob, width, height);\n return asset.imageBitmap;\n } catch (err) {\n throw new Error(\n `Asset ${assetId} has no video track and is not a decodable image: ${(err as Error).message}`,\n );\n }\n }\n asset.videoSink = new CanvasSink(videoTrack, {\n width,\n height,\n fit: 'contain',\n poolSize: 5,\n // Preserve source alpha — required for transparent videos (e.g. WebM\n // with VP8/VP9 alpha, HEVC/AV1 with alpha). With `alpha: false`\n // (mediabunny default) CanvasSink flattens transparent pixels to a\n // black background, breaking compositing of alpha videos.\n alpha: true,\n });\n }\n const result = await asset.videoSink.getCanvas(time);\n if (!result) throw new Error(`Failed to decode frame at ${time}s for asset ${assetId}`);\n return result.canvas;\n },\n\n async decodeAudio(assetId) {\n const asset = await getOrCreateAsset(assetId);\n if (asset.audioBuffer) return asset.audioBuffer;\n\n // Fast path: try browser-native decodeAudioData on the whole blob.\n // This works for standalone audio files (mp3/wav/flac/ogg/m4a) and is\n // much simpler and faster than iterating through MediaBunny packets.\n try {\n const arrayBuffer = await asset.blob.arrayBuffer();\n // decodeAudioData detaches the buffer, so pass a copy (slice(0) is cheap).\n const buffer = await audioContext.decodeAudioData(arrayBuffer.slice(0));\n asset.audioBuffer = buffer;\n return buffer;\n } catch {\n // Fall through to MediaBunny path — typical for video containers whose\n // audio track the browser's decodeAudioData can't extract directly.\n }\n\n const audioTrack = await asset.input.getPrimaryAudioTrack();\n if (!audioTrack) throw new Error(`No audio track in asset ${assetId}`);\n\n // Iterate the full track via AudioBufferSink.buffers() and concatenate\n // into one AudioBuffer. getBuffer(0) alone only returns the first packet.\n const sink = new AudioBufferSink(audioTrack);\n const chunks: AudioBuffer[] = [];\n let totalFrames = 0;\n let sampleRate = 0;\n let numberOfChannels = 0;\n for await (const wrapped of sink.buffers()) {\n const buf = wrapped.buffer;\n chunks.push(buf);\n totalFrames += buf.length;\n if (!sampleRate) sampleRate = buf.sampleRate;\n if (!numberOfChannels) numberOfChannels = buf.numberOfChannels;\n }\n if (chunks.length === 0 || totalFrames === 0) {\n throw new Error(`Failed to decode audio for asset ${assetId}`);\n }\n\n const merged = audioContext.createBuffer(numberOfChannels, totalFrames, sampleRate);\n let offset = 0;\n for (const chunk of chunks) {\n for (let ch = 0; ch < numberOfChannels; ch++) {\n const src = chunk.getChannelData(Math.min(ch, chunk.numberOfChannels - 1));\n merged.copyToChannel(src, ch, offset);\n }\n offset += chunk.length;\n }\n asset.audioBuffer = merged;\n return merged;\n },\n\n async getMediaInfo(assetId) {\n const asset = await getOrCreateAsset(assetId);\n if (asset.mediaInfo) return asset.mediaInfo;\n const videoTrack = await asset.input.getPrimaryVideoTrack();\n const audioTrack = await asset.input.getPrimaryAudioTrack();\n const duration = await asset.input.computeDuration();\n let fps = 0;\n if (videoTrack) {\n const stats = await videoTrack.computePacketStats(100);\n fps = stats.averagePacketRate;\n }\n const info: MediaInfo = {\n duration,\n width: videoTrack?.displayWidth ?? 0,\n height: videoTrack?.displayHeight ?? 0,\n fps,\n hasVideo: videoTrack !== null,\n hasAudio: audioTrack !== null,\n videoCodec: videoTrack?.codec ?? null,\n audioCodec: audioTrack?.codec ?? null,\n sampleRate: audioTrack?.sampleRate ?? 0,\n channels: audioTrack?.numberOfChannels ?? 0,\n };\n asset.mediaInfo = info;\n return info;\n },\n\n destroy() {\n for (const asset of cache.values()) {\n asset.input.dispose();\n asset.imageBitmap?.close();\n }\n cache.clear();\n initPromises.clear();\n },\n };\n}\n","import type { Compositor, CompositeLayer } from './types.js';\n\nexport function createCanvas2DCompositor(width: number, height: number): Compositor {\n // `{ alpha: true }` keeps the backing buffer's alpha channel, so transparent\n // pixels from source layers (alpha videos, PNGs, letterboxed content) remain\n // transparent in the composed ImageBitmap instead of being flattened to black.\n let canvas = new OffscreenCanvas(width, height);\n let ctx = canvas.getContext('2d', { alpha: true })!;\n let currentWidth = width;\n let currentHeight = height;\n\n return {\n async composite(layers: CompositeLayer[]): Promise<ImageBitmap> {\n ctx.clearRect(0, 0, currentWidth, currentHeight);\n\n // Sort by zIndex ascending (low = bottom, high = top)\n const sorted = [...layers].sort((a, b) => a.zIndex - b.zIndex);\n\n for (const layer of sorted) {\n if (layer.opacity <= 0) continue;\n\n ctx.globalAlpha = layer.opacity;\n ctx.drawImage(layer.source, 0, 0, currentWidth, currentHeight);\n }\n\n ctx.globalAlpha = 1;\n return createImageBitmap(canvas);\n },\n\n resize(w: number, h: number): void {\n currentWidth = w;\n currentHeight = h;\n canvas = new OffscreenCanvas(w, h);\n ctx = canvas.getContext('2d', { alpha: true })!;\n },\n\n destroy(): void {\n // OffscreenCanvas has no explicit cleanup needed\n },\n };\n}\n","import type { Compositor, CompositeLayer } from './types.js';\n\nconst VERTEX_SHADER = /* wgsl */ `\nstruct VertexOutput {\n @builtin(position) position: vec4f,\n @location(0) texCoord: vec2f,\n};\n\n@vertex\nfn main(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {\n // Full-screen quad: 6 vertices, 2 triangles\n var positions = array<vec2f, 6>(\n vec2f(-1.0, -1.0),\n vec2f( 1.0, -1.0),\n vec2f(-1.0, 1.0),\n vec2f(-1.0, 1.0),\n vec2f( 1.0, -1.0),\n vec2f( 1.0, 1.0),\n );\n\n var texCoords = array<vec2f, 6>(\n vec2f(0.0, 1.0),\n vec2f(1.0, 1.0),\n vec2f(0.0, 0.0),\n vec2f(0.0, 0.0),\n vec2f(1.0, 1.0),\n vec2f(1.0, 0.0),\n );\n\n var output: VertexOutput;\n output.position = vec4f(positions[vertexIndex], 0.0, 1.0);\n output.texCoord = texCoords[vertexIndex];\n return output;\n}\n`;\n\nconst FRAGMENT_SHADER = /* wgsl */ `\n@group(0) @binding(0) var texSampler: sampler;\n@group(0) @binding(1) var texSource: texture_2d<f32>;\n@group(0) @binding(2) var<uniform> opacity: f32;\n\n@fragment\nfn main(@location(0) texCoord: vec2f) -> @location(0) vec4f {\n var color = textureSample(texSource, texSampler, texCoord);\n // Multiply alpha by uniform opacity\n color.a = color.a * opacity;\n // Premultiply RGB by the new alpha\n color = vec4f(color.rgb * color.a, color.a);\n return color;\n}\n`;\n\nexport async function createGPUCompositor(width: number, height: number): Promise<Compositor> {\n if (!navigator.gpu) {\n throw new Error('WebGPU not available');\n }\n const adapter = await navigator.gpu.requestAdapter();\n if (!adapter) {\n throw new Error('Failed to get WebGPU adapter');\n }\n\n const device = await adapter.requestDevice();\n\n // Create sampler\n const sampler = device.createSampler({\n magFilter: 'linear',\n minFilter: 'linear',\n });\n\n // Create uniform buffer for opacity (f32 = 4 bytes, aligned to 16)\n const opacityBuffer = device.createBuffer({\n size: 16,\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n });\n\n // Create bind group layout\n const bindGroupLayout = device.createBindGroupLayout({\n entries: [\n { binding: 0, visibility: GPUShaderStage.FRAGMENT, sampler: {} },\n { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: {} },\n { binding: 2, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } },\n ],\n });\n\n const pipelineLayout = device.createPipelineLayout({\n bindGroupLayouts: [bindGroupLayout],\n });\n\n // Create shader modules\n const vertexModule = device.createShaderModule({ code: VERTEX_SHADER });\n const fragmentModule = device.createShaderModule({ code: FRAGMENT_SHADER });\n\n // Create render pipeline with premultiplied alpha blending\n const pipeline = device.createRenderPipeline({\n layout: pipelineLayout,\n vertex: {\n module: vertexModule,\n entryPoint: 'main',\n },\n fragment: {\n module: fragmentModule,\n entryPoint: 'main',\n targets: [{\n format: 'rgba8unorm',\n blend: {\n color: {\n srcFactor: 'one',\n dstFactor: 'one-minus-src-alpha',\n operation: 'add',\n },\n alpha: {\n srcFactor: 'one',\n dstFactor: 'one-minus-src-alpha',\n operation: 'add',\n },\n },\n }],\n },\n primitive: {\n topology: 'triangle-list',\n },\n });\n\n // Create output texture\n let currentWidth = width;\n let currentHeight = height;\n let outputTexture = createOutputTexture(device, width, height);\n\n function createOutputTexture(dev: GPUDevice, w: number, h: number): GPUTexture {\n return dev.createTexture({\n size: { width: w, height: h },\n format: 'rgba8unorm',\n usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC | GPUTextureUsage.TEXTURE_BINDING,\n });\n }\n\n return {\n async composite(layers: CompositeLayer[]): Promise<ImageBitmap> {\n const sorted = [...layers]\n .filter((l) => l.opacity > 0)\n .sort((a, b) => a.zIndex - b.zIndex);\n\n const encoder = device.createCommandEncoder();\n const outputView = outputTexture.createView();\n\n const layerTextures: GPUTexture[] = [];\n\n if (sorted.length === 0) {\n // Clear pass only\n const pass = encoder.beginRenderPass({\n colorAttachments: [{\n view: outputView,\n loadOp: 'clear',\n storeOp: 'store',\n clearValue: { r: 0, g: 0, b: 0, a: 0 },\n }],\n });\n pass.end();\n } else {\n for (let i = 0; i < sorted.length; i++) {\n const layer = sorted[i];\n\n // Create texture from source\n const layerTexture = device.createTexture({\n size: { width: currentWidth, height: currentHeight },\n format: 'rgba8unorm',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,\n });\n layerTextures.push(layerTexture);\n\n device.queue.copyExternalImageToTexture(\n { source: layer.source as ImageBitmap },\n { texture: layerTexture },\n { width: currentWidth, height: currentHeight },\n );\n\n // Write opacity uniform\n const opacityData = new Float32Array([layer.opacity]);\n device.queue.writeBuffer(opacityBuffer, 0, opacityData);\n\n // Create bind group for this layer\n const bindGroup = device.createBindGroup({\n layout: bindGroupLayout,\n entries: [\n { binding: 0, resource: sampler },\n { binding: 1, resource: layerTexture.createView() },\n { binding: 2, resource: { buffer: opacityBuffer } },\n ],\n });\n\n // Render pass: first pass clears, subsequent passes load\n const loadOp: 'load' | 'clear' = i === 0 ? 'clear' : 'load';\n const pass = encoder.beginRenderPass({\n colorAttachments: [{\n view: outputView,\n loadOp,\n storeOp: 'store',\n ...(loadOp === 'clear' ? { clearValue: { r: 0, g: 0, b: 0, a: 0 } } : {}),\n }],\n });\n\n pass.setPipeline(pipeline);\n pass.setBindGroup(0, bindGroup);\n pass.draw(6);\n pass.end();\n }\n }\n\n // Copy output texture to staging buffer for CPU readback\n const bytesPerRow = Math.ceil(currentWidth * 4 / 256) * 256;\n const bufferSize = bytesPerRow * currentHeight;\n const stagingBuffer = device.createBuffer({\n size: bufferSize,\n usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,\n });\n\n encoder.copyTextureToBuffer(\n { texture: outputTexture },\n { buffer: stagingBuffer, bytesPerRow },\n { width: currentWidth, height: currentHeight },\n );\n\n device.queue.submit([encoder.finish()]);\n\n // Destroy layer textures after command submission\n for (const tex of layerTextures) {\n tex.destroy();\n }\n\n // Read back pixels from staging buffer\n await stagingBuffer.mapAsync(GPUMapMode.READ);\n const rawData = stagingBuffer.getMappedRange();\n\n // Copy pixel data row by row (bytesPerRow may have padding)\n const pixelData = new Uint8ClampedArray(currentWidth * currentHeight * 4);\n const src = new Uint8Array(rawData);\n for (let row = 0; row < currentHeight; row++) {\n const srcOffset = row * bytesPerRow;\n const dstOffset = row * currentWidth * 4;\n pixelData.set(src.subarray(srcOffset, srcOffset + currentWidth * 4), dstOffset);\n }\n\n stagingBuffer.unmap();\n stagingBuffer.destroy();\n\n // The fragment shader outputs premultiplied RGB (see FRAGMENT_SHADER),\n // so `pixelData` is in premultiplied form. `putImageData` treats its\n // input as straight (unpremultiplied) alpha — feeding premultiplied\n // bytes in would cause the canvas to double-premultiply at semi-\n // transparent pixels, producing darkened/wrong colors on alpha-blended\n // edges. Unpremultiply so the straight-alpha ImageData contract holds.\n for (let i = 0; i < pixelData.length; i += 4) {\n const a = pixelData[i + 3];\n if (a > 0 && a < 255) {\n const inv = 255 / a;\n pixelData[i] = Math.min(255, Math.round(pixelData[i] * inv));\n pixelData[i + 1] = Math.min(255, Math.round(pixelData[i + 1] * inv));\n pixelData[i + 2] = Math.min(255, Math.round(pixelData[i + 2] * inv));\n }\n }\n\n // `{ alpha: true }` keeps the readback canvas's alpha channel so a fully\n // transparent output texture stays transparent (default `alpha: false`\n // would flatten it to opaque black).\n const readbackCanvas = new OffscreenCanvas(currentWidth, currentHeight);\n const ctx = readbackCanvas.getContext('2d', { alpha: true })!;\n const imageData = new ImageData(pixelData, currentWidth, currentHeight);\n ctx.putImageData(imageData, 0, 0);\n return createImageBitmap(readbackCanvas);\n },\n\n resize(w: number, h: number): void {\n outputTexture.destroy();\n currentWidth = w;\n currentHeight = h;\n outputTexture = createOutputTexture(device, w, h);\n },\n\n destroy(): void {\n outputTexture.destroy();\n opacityBuffer.destroy();\n device.destroy();\n },\n };\n}\n","import type { Compositor } from './types.js';\nimport { createCanvas2DCompositor } from './canvas2d-compositor.js';\nimport { createGPUCompositor } from './gpu-compositor.js';\n\nexport type CompositorType = 'gpu' | 'canvas2d' | 'auto';\n\nexport async function createCompositor(\n width: number,\n height: number,\n type: CompositorType = 'auto',\n): Promise<Compositor> {\n if (type === 'canvas2d') {\n return createCanvas2DCompositor(width, height);\n }\n\n if (type === 'gpu' || type === 'auto') {\n if (typeof navigator !== 'undefined' && (navigator as unknown as { gpu?: unknown }).gpu) {\n try {\n return await createGPUCompositor(width, height);\n } catch {\n if (type === 'gpu') {\n throw new Error('WebGPU compositor requested but initialization failed');\n }\n }\n } else if (type === 'gpu') {\n throw new Error('WebGPU not available');\n }\n }\n\n return createCanvas2DCompositor(width, height);\n}\n","import type {\n FrameRenderer,\n RenderedFrame,\n MediaDecoder,\n Compositor,\n CompositeLayer,\n SubtitleRenderer,\n} from './types.js';\nimport type { Composition } from '@pneuma-craft/timeline';\nimport { resolveFrame } from '@pneuma-craft/timeline';\n\nexport function createFrameRenderer(\n decoder: MediaDecoder,\n compositor: Compositor,\n width: number,\n height: number,\n subtitleRenderer?: SubtitleRenderer,\n): FrameRenderer {\n return {\n async renderFrame(composition: Composition, time: number): Promise<RenderedFrame> {\n const resolved = resolveFrame(composition, time);\n\n const videoClips = resolved.clips.filter(rc => rc.track.type === 'video');\n // Subtitle clips are skipped entirely when no renderer is wired up — this\n // preserves the pre-0.3 behavior for consumers that overlay subtitles\n // outside the compositor (e.g., as a DOM layer over the preview canvas).\n const subtitleClips = subtitleRenderer\n ? resolved.clips.filter(rc => rc.track.type === 'subtitle')\n : [];\n\n const layers: CompositeLayer[] = [];\n\n for (let i = 0; i < videoClips.length; i++) {\n const rc = videoClips[i];\n const source = await decoder.decodeVideoFrame(\n rc.clip.assetId,\n rc.localTime,\n width,\n height,\n );\n layers.push({\n source,\n opacity: 1,\n zIndex: i,\n });\n }\n\n // Subtitles always composite on top of every video layer, regardless of\n // the track ordering in the composition. Track ordering between multiple\n // subtitle tracks is preserved.\n if (subtitleRenderer && subtitleClips.length > 0) {\n const baseZ = videoClips.length;\n for (let i = 0; i < subtitleClips.length; i++) {\n const rc = subtitleClips[i];\n const rendered = await subtitleRenderer({\n clip: rc.clip,\n localTime: rc.localTime,\n width,\n height,\n });\n if (rendered) {\n layers.push({\n source: rendered,\n opacity: 1,\n zIndex: baseZ + i,\n });\n }\n }\n }\n\n const image = await compositor.composite(layers);\n\n return { image, time, width, height };\n },\n\n destroy(): void {\n decoder.destroy();\n compositor.destroy();\n },\n };\n}\n","import type { MasterClock, ClockState } from './types.js';\n\nexport interface MasterClockOptions {\n audioContext: AudioContext;\n duration?: number;\n frameRate?: number;\n}\n\nexport function createMasterClock(options: MasterClockOptions): MasterClock {\n const audioContext = options.audioContext;\n\n let state: ClockState = 'stopped';\n let _duration = options.duration ?? 0;\n let _playbackRate = 1;\n let _loop: { start: number; end: number } | null = null;\n\n // Timing anchors\n let startAudioContextTime = 0;\n let startTimelineTime = 0;\n let pausedAt = 0;\n\n // Drift tracking\n let _driftMs = 0;\n\n // Subscribers\n const timeUpdateListeners = new Set<(time: number) => void>();\n const stateChangeListeners = new Set<(state: ClockState) => void>();\n\n function computeCurrentTime(): number {\n if (state !== 'playing') return pausedAt;\n\n const elapsed = (audioContext.currentTime - startAudioContextTime) * _playbackRate;\n let time = startTimelineTime + elapsed;\n\n if (_loop && _loop.end > _loop.start && time >= _loop.end) {\n const loopDuration = _loop.end - _loop.start;\n time = _loop.start + ((time - _loop.start) % loopDuration);\n }\n\n return Math.max(0, Math.min(time, _duration || Infinity));\n }\n\n function notifyTimeUpdate(time: number): void {\n for (const cb of timeUpdateListeners) {\n try { cb(time); } catch (e) { console.error('[MasterClock] listener error:', e); }\n }\n }\n\n function notifyStateChange(newState: ClockState): void {\n for (const cb of stateChangeListeners) {\n try { cb(newState); } catch (e) { console.error('[MasterClock] listener error:', e); }\n }\n }\n\n function setState(newState: ClockState): void {\n if (state === newState) return;\n state = newState;\n notifyStateChange(newState);\n }\n\n const clock: MasterClock = {\n get currentTime() { return computeCurrentTime(); },\n get state() { return state; },\n get driftMs() { return _driftMs; },\n\n get playbackRate() { return _playbackRate; },\n set playbackRate(rate: number) {\n const clamped = Math.max(0.1, Math.min(rate, 16));\n if (state === 'playing') {\n const current = computeCurrentTime();\n startTimelineTime = current;\n startAudioContextTime = audioContext.currentTime;\n }\n _playbackRate = clamped;\n },\n\n get duration() { return _duration; },\n set duration(d: number) { _duration = Math.max(0, d); },\n\n get loop() { return _loop; },\n set loop(l) { _loop = l; },\n\n play(): void {\n if (state === 'playing') return;\n startTimelineTime = pausedAt;\n startAudioContextTime = audioContext.currentTime;\n setState('playing');\n if (audioContext.state === 'suspended') {\n audioContext.resume();\n }\n },\n\n pause(): void {\n if (state !== 'playing') return;\n pausedAt = computeCurrentTime();\n setState('paused');\n },\n\n seek(time: number): void {\n const clamped = Math.max(0, Math.min(time, _duration));\n if (state === 'playing') {\n startTimelineTime = clamped;\n startAudioContextTime = audioContext.currentTime;\n } else {\n pausedAt = clamped;\n }\n notifyTimeUpdate(clamped);\n },\n\n reportVideoTime(videoTime: number): void {\n const audioTime = computeCurrentTime();\n _driftMs = (audioTime - videoTime) * 1000;\n },\n\n onTimeUpdate(cb) {\n timeUpdateListeners.add(cb);\n return () => { timeUpdateListeners.delete(cb); };\n },\n\n onStateChange(cb) {\n stateChangeListeners.add(cb);\n return () => { stateChangeListeners.delete(cb); };\n },\n\n destroy(): void {\n if (state === 'playing') {\n pausedAt = computeCurrentTime();\n }\n state = 'stopped';\n timeUpdateListeners.clear();\n stateChangeListeners.clear();\n _driftMs = 0;\n },\n };\n\n return clock;\n}\n","import type { AudioScheduler } from './types.js';\nimport type { Composition, Clip, Track } from '@pneuma-craft/timeline';\n\nexport interface AudioSchedulerOptions {\n audioContext: AudioContext;\n}\n\ninterface ActiveSource {\n source: AudioBufferSourceNode;\n clipGain: GainNode;\n}\n\nexport function createAudioScheduler(options: AudioSchedulerOptions): AudioScheduler {\n const { audioContext } = options;\n\n // Buffer storage: clipId → AudioBuffer\n const clipBuffers = new Map<string, AudioBuffer>();\n\n // Per-track gain nodes: trackId → GainNode\n const trackGains = new Map<string, GainNode>();\n\n // Per-track volume (separate from mute): trackId → volume\n const trackVolumes = new Map<string, number>();\n\n // Per-track muted state: trackId → muted\n const trackMutedStates = new Map<string, boolean>();\n\n // Currently active sources\n let activeSources: ActiveSource[] = [];\n\n // Scheduler tick state for future clip scheduling\n let schedulerIntervalId: ReturnType<typeof setInterval> | null = null;\n let scheduledClipIds = new Set<string>();\n let currentComposition: Composition | null = null;\n\n // Playback rate (applied to elapsed time and source nodes)\n let _playbackRate = 1;\n\n // Whether playback is currently active\n let _isPlaying = false;\n\n // Previous timeline time for loop-wrap detection\n let _prevTimelineTime = 0;\n\n // Callback to get current timeline time from the master clock (loop-aware)\n let _getCurrentTime: (() => number) | null = null;\n\n // Look-ahead window in seconds\n const LOOK_AHEAD = 0.2;\n\n // Master gain node connected to destination\n const masterGain = audioContext.createGain();\n masterGain.gain.value = 1;\n masterGain.connect(audioContext.destination);\n\n function getOrCreateTrackGain(track: Track): GainNode {\n let node = trackGains.get(track.id);\n if (!node) {\n node = audioContext.createGain();\n // Use runtime overrides if they exist, otherwise fall back to composition data\n const vol = trackVolumes.get(track.id) ?? track.volume ?? 1;\n const muted = trackMutedStates.get(track.id) ?? track.muted;\n node.gain.value = muted ? 0 : vol;\n trackVolumes.set(track.id, vol);\n node.connect(masterGain);\n trackGains.set(track.id, node);\n }\n return node;\n }\n\n function clearSchedulerTick(): void {\n if (schedulerIntervalId !== null) {\n clearInterval(schedulerIntervalId);\n schedulerIntervalId = null;\n }\n }\n\n function stopAllSources(): void {\n for (const { source } of activeSources) {\n try {\n source.stop();\n } catch {\n // Source may already be stopped\n }\n source.disconnect();\n }\n activeSources = [];\n }\n\n function isClipActiveAt(clip: Clip, fromTime: number): boolean {\n return clip.startTime <= fromTime && fromTime < clip.startTime + clip.duration;\n }\n\n function scheduleClip(clip: Clip, track: Track, fromTime: number): void {\n const buffer = clipBuffers.get(clip.id);\n if (!buffer) return;\n\n const trackGain = getOrCreateTrackGain(track);\n\n // Create per-clip gain node\n const clipGain = audioContext.createGain();\n const clipVolume = clip.volume ?? 1;\n clipGain.gain.value = clipVolume;\n\n // Create source node\n const source = audioContext.createBufferSource();\n source.buffer = buffer;\n\n // Apply playback rate to source node\n source.playbackRate.value = _playbackRate;\n\n // Connect: source → clipGain → trackGain → masterGain → destination\n source.connect(clipGain);\n clipGain.connect(trackGain);\n\n const now = audioContext.currentTime;\n\n // Determine if the clip starts in the future relative to current timeline time\n let contextStartTime: number;\n let sourceOffset: number;\n let remainingDuration: number;\n\n if (clip.startTime > fromTime) {\n // Future clip: schedule it to start at the correct AudioContext time\n // Divide by playbackRate: at 2x rate, 1 timeline second = 0.5 real seconds\n const timeUntilClipStart = (clip.startTime - fromTime) / _playbackRate;\n contextStartTime = now + timeUntilClipStart;\n sourceOffset = clip.inPoint;\n remainingDuration = clip.duration;\n } else {\n // Already active clip: start immediately with offset\n contextStartTime = 0;\n const elapsed = fromTime - clip.startTime;\n sourceOffset = clip.inPoint + elapsed;\n remainingDuration = clip.duration - elapsed;\n }\n\n // Apply fade automations\n // Fade durations must be divided by playbackRate since AudioContext runs in real time\n if (clip.fadeIn !== undefined && clip.fadeIn > 0) {\n const realFadeIn = clip.fadeIn / _playbackRate;\n if (clip.startTime > fromTime) {\n // Future clip: anchor fade to contextStartTime\n clipGain.gain.setValueAtTime(0, contextStartTime);\n clipGain.gain.linearRampToValueAtTime(clipVolume, contextStartTime + realFadeIn);\n } else {\n const fadeInEnd = clip.startTime + clip.fadeIn;\n if (fromTime < fadeInEnd) {\n // Still within fade-in region\n const fadeElapsed = fromTime - clip.startTime;\n const currentLevel = fadeElapsed > 0 ? (fadeElapsed / clip.fadeIn) * clipVolume : 0;\n const fadeRemaining = (clip.fadeIn - fadeElapsed) / _playbackRate;\n clipGain.gain.setValueAtTime(currentLevel, now);\n clipGain.gain.linearRampToValueAtTime(clipVolume, now + fadeRemaining);\n }\n }\n }\n\n if (clip.fadeOut !== undefined && clip.fadeOut > 0) {\n const realFadeOut = clip.fadeOut / _playbackRate;\n const realDuration = clip.duration / _playbackRate;\n if (clip.startTime > fromTime) {\n // Future clip: anchor fade to contextStartTime\n const fadeOutStart = contextStartTime + realDuration - realFadeOut;\n clipGain.gain.setValueAtTime(clipVolume, fadeOutStart);\n clipGain.gain.linearRampToValueAtTime(0, contextStartTime + realDuration);\n } else {\n const fadeOutStart = clip.startTime + clip.duration - clip.fadeOut;\n const fadeOutContextTime = now + Math.max(0, (fadeOutStart - fromTime) / _playbackRate);\n clipGain.gain.setValueAtTime(clipVolume, fadeOutContextTime);\n clipGain.gain.linearRampToValueAtTime(0, fadeOutContextTime + realFadeOut);\n }\n }\n\n source.start(contextStartTime, sourceOffset, remainingDuration);\n\n activeSources.push({ source, clipGain });\n }\n\n function isClipInLookAhead(clip: Clip, currentTime: number, lookAhead: number): boolean {\n const clipEnd = clip.startTime + clip.duration;\n // Clip starts within the look-ahead window and hasn't ended\n return clip.startTime > currentTime && clip.startTime <= currentTime + lookAhead && clipEnd > currentTime;\n }\n\n function scheduleComposition(fromTime: number, composition: Composition): void {\n for (const track of composition.tracks) {\n // Both audio tracks and video tracks contribute audio — a video clip's\n // embedded audio track gets scheduled alongside pure audio clips. Skip\n // anything that isn't a media track (e.g. subtitle) and anything that\n // the user has muted. Clips whose asset has no audio track never get a\n // buffer loaded (see loadClip in playback-engine.ts), so `scheduleClip`\n // naturally skips them.\n if (track.type !== 'audio' && track.type !== 'video') continue;\n if (track.muted) continue;\n\n for (const clip of track.clips) {\n if (!isClipActiveAt(clip, fromTime)) continue;\n if (scheduledClipIds.has(clip.id)) continue;\n scheduledClipIds.add(clip.id);\n scheduleClip(clip, track, fromTime);\n }\n }\n }\n\n function startSchedulerTick(fromTime: number, composition: Composition): void {\n clearSchedulerTick();\n currentComposition = composition;\n\n // Fallback values for computing timeline time when no getCurrentTime callback\n const tickStartContextTime = audioContext.currentTime;\n const tickStartFromTime = fromTime;\n\n schedulerIntervalId = setInterval(() => {\n if (!currentComposition) return;\n\n // Use clock's getCurrentTime if available (loop-aware), otherwise fallback\n let currentTimelineTime: number;\n if (_getCurrentTime) {\n currentTimelineTime = _getCurrentTime();\n } else {\n const elapsed = (audioContext.currentTime - tickStartContextTime) * _playbackRate;\n currentTimelineTime = tickStartFromTime + elapsed;\n }\n\n // Detect loop wrap: timeline time went backwards\n if (currentTimelineTime < _prevTimelineTime) {\n scheduledClipIds.clear();\n stopAllSources();\n }\n _prevTimelineTime = currentTimelineTime;\n\n for (const track of currentComposition.tracks) {\n if (track.type !== 'audio') continue;\n if (track.muted) continue;\n\n for (const clip of track.clips) {\n if (scheduledClipIds.has(clip.id)) continue;\n if (isClipInLookAhead(clip, currentTimelineTime, LOOK_AHEAD) || isClipActiveAt(clip, currentTimelineTime)) {\n scheduledClipIds.add(clip.id);\n scheduleClip(clip, track, currentTimelineTime);\n }\n }\n }\n }, 100);\n }\n\n const scheduler: AudioScheduler = {\n get audioContext() {\n return audioContext;\n },\n\n loadClip(clipId: string, audioBuffer: AudioBuffer): void {\n clipBuffers.set(clipId, audioBuffer);\n },\n\n play(fromTime: number, composition: Composition, getCurrentTime?: () => number): void {\n stopAllSources();\n clearSchedulerTick();\n scheduledClipIds = new Set();\n _prevTimelineTime = fromTime;\n _getCurrentTime = getCurrentTime ?? null;\n _isPlaying = true;\n currentComposition = composition;\n scheduleComposition(fromTime, composition);\n startSchedulerTick(fromTime, composition);\n },\n\n pause(): void {\n clearSchedulerTick();\n stopAllSources();\n scheduledClipIds = new Set();\n _isPlaying = false;\n },\n\n seek(time: number, composition: Composition): void {\n clearSchedulerTick();\n stopAllSources();\n scheduledClipIds = new Set();\n _prevTimelineTime = time;\n scheduleComposition(time, composition);\n startSchedulerTick(time, composition);\n },\n\n setPlaybackRate(rate: number): void {\n _playbackRate = rate;\n // If playing, reschedule everything with new rate so future clip\n // contextStartTime values are recalculated correctly.\n if (_isPlaying && currentComposition && _getCurrentTime) {\n stopAllSources();\n scheduledClipIds = new Set();\n const currentTime = _getCurrentTime();\n scheduleComposition(currentTime, currentComposition);\n clearSchedulerTick();\n startSchedulerTick(currentTime, currentComposition);\n } else {\n // Not playing or no getCurrentTime — just update active sources\n for (const scheduled of activeSources) {\n scheduled.source.playbackRate.value = rate;\n }\n }\n },\n\n setTrackVolume(trackId: string, volume: number): void {\n trackVolumes.set(trackId, volume);\n const node = trackGains.get(trackId);\n if (node) {\n // Only apply gain if the track is not muted\n const isMuted = trackMutedStates.get(trackId) ?? false;\n if (!isMuted) {\n node.gain.setValueAtTime(volume, audioContext.currentTime);\n }\n }\n },\n\n setTrackMute(trackId: string, muted: boolean): void {\n trackMutedStates.set(trackId, muted);\n const node = trackGains.get(trackId);\n if (node) {\n if (muted) {\n node.gain.value = 0;\n } else {\n const vol = trackVolumes.get(trackId) ?? 1;\n node.gain.value = vol;\n }\n }\n },\n\n destroy(): void {\n clearSchedulerTick();\n stopAllSources();\n scheduledClipIds = new Set();\n currentComposition = null;\n _isPlaying = false;\n for (const node of trackGains.values()) {\n node.disconnect();\n }\n trackGains.clear();\n trackVolumes.clear();\n trackMutedStates.clear();\n clipBuffers.clear();\n masterGain.disconnect();\n },\n };\n\n return scheduler;\n}\n","import type {\n PlaybackEngine,\n PlaybackState,\n RenderedFrame,\n AssetResolver,\n MediaDecoder,\n Compositor,\n FrameRenderer,\n MasterClock,\n AudioScheduler,\n SubtitleRenderer,\n} from './types.js';\nimport type { Composition } from '@pneuma-craft/timeline';\nimport { createMediaDecoder } from './media-decoder.js';\nimport { createCompositor, type CompositorType } from './compositor.js';\nimport { createFrameRenderer } from './frame-renderer.js';\nimport { createMasterClock } from './master-clock.js';\nimport { createAudioScheduler } from './audio-scheduler.js';\n\nexport interface PlaybackEngineOptions {\n compositorType?: CompositorType;\n /** Max time a single frame decode may block before the loop skips it. Default 1000ms. */\n decodeTimeoutMs?: number;\n /**\n * Optional rasterizer for subtitle-track clips. When provided, active\n * subtitle clips are composited on top of the video layers — the same code\n * path used for export, so preview and export stay in lockstep. When\n * omitted, subtitle tracks render nothing (legacy behavior).\n */\n subtitleRenderer?: SubtitleRenderer;\n}\n\n/** Default per-frame decode timeout. Decodes exceeding this are logged and skipped. */\nconst DEFAULT_DECODE_TIMEOUT_MS = 1000;\n\n/** Enables verbose rAF-loop logs for debugging playback stalls. */\nconst DEBUG_LOOP = typeof globalThis !== 'undefined'\n && (globalThis as { __PNEUMA_CRAFT_PLAYBACK_DEBUG__?: boolean }).__PNEUMA_CRAFT_PLAYBACK_DEBUG__ === true;\n\nexport function createPlaybackEngine(options?: PlaybackEngineOptions): PlaybackEngine {\n const compositorType = options?.compositorType ?? 'canvas2d';\n const decodeTimeoutMs = options?.decodeTimeoutMs ?? DEFAULT_DECODE_TIMEOUT_MS;\n const subtitleRenderer = options?.subtitleRenderer;\n\n let _state: PlaybackState = 'idle';\n let _playbackRate = 1;\n let _loop: { start: number; end: number } | null = null;\n\n // Subsystems (created on load)\n let _decoder: MediaDecoder | null = null;\n let _compositor: Compositor | null = null;\n let _frameRenderer: FrameRenderer | null = null;\n let _clock: MasterClock | null = null;\n let _audioScheduler: AudioScheduler | null = null;\n let _audioContext: AudioContext | null = null;\n let _composition: Composition | null = null;\n\n // rAF handle\n let _rafId: number | null = null;\n\n // True while a frame decode is awaiting — prevents stacking concurrent renders\n // when one decode runs slower than a rAF tick.\n let _frameInFlight = false;\n\n // Seek ID counter for discarding stale paused-seek renders\n let _seekId = 0;\n\n // Subscribers\n const stateChangeListeners = new Set<(state: PlaybackState) => void>();\n const timeUpdateListeners = new Set<(time: number) => void>();\n const frameRenderedListeners = new Set<(frame: RenderedFrame) => void>();\n\n function setState(newState: PlaybackState): void {\n if (_state === newState) return;\n _state = newState;\n for (const cb of stateChangeListeners) {\n try { cb(newState); } catch (e) { console.error('[PlaybackEngine] listener error:', e); }\n }\n }\n\n function emitTimeUpdate(time: number): void {\n for (const cb of timeUpdateListeners) {\n try { cb(time); } catch (e) { console.error('[PlaybackEngine] listener error:', e); }\n }\n }\n\n function emitFrameRendered(frame: RenderedFrame): void {\n for (const cb of frameRenderedListeners) {\n try { cb(frame); } catch (e) { console.error('[PlaybackEngine] listener error:', e); }\n }\n }\n\n function destroySubsystems(): void {\n if (_rafId !== null) {\n cancelAnimationFrame(_rafId);\n _rafId = null;\n }\n _frameInFlight = false;\n _frameRenderer?.destroy();\n _clock?.destroy();\n _audioScheduler?.destroy();\n _audioContext?.close();\n _decoder = null;\n _compositor = null;\n _frameRenderer = null;\n _clock = null;\n _audioScheduler = null;\n _audioContext = null;\n _composition = null;\n }\n\n function startRafLoop(): void {\n if (_rafId !== null) return;\n\n // The loop is designed so that time updates and rAF scheduling are INDEPENDENT\n // of frame decoding. This means:\n // 1. If a decode hangs or is slow, the clock/slider still advances and pause/seek\n // remain responsive.\n // 2. At most one decode runs at a time (no concurrent pile-up).\n // 3. A decode exceeding `decodeTimeoutMs` is logged and skipped so the loop can\n // recover from a stuck codec packet pump.\n let lastLoggedState: 'render' | 'skip-inflight' | null = null;\n const logStateChange = (state: 'render' | 'skip-inflight', time: number, extra?: string): void => {\n if (!DEBUG_LOOP) return;\n if (lastLoggedState === state) return;\n lastLoggedState = state;\n console.log(`[engine] ${state} @ t=${time.toFixed(3)}${extra ? ' ' + extra : ''}`);\n };\n\n const loop = (): void => {\n _rafId = null;\n if (_state !== 'playing' || !_clock || !_frameRenderer || !_composition) return;\n\n const time = _clock.currentTime;\n\n // Emit time update up-front — consumers see the clock tick regardless of decode state.\n emitTimeUpdate(time);\n\n // End-of-timeline check.\n if (!_clock.loop && time >= _composition.duration) {\n engine.pause();\n return;\n }\n\n // Always schedule the next rAF synchronously so timing is decoupled from decode.\n _rafId = requestAnimationFrame(loop);\n\n // Skip this tick's render if the previous decode is still in flight — back-pressure\n // so slow decodes don't stack concurrent calls. The watchdog below recovers if a\n // decode never resolves; we never get stuck.\n //\n // NOTE: we intentionally do NOT skip based on _clock.driftMs. A prior implementation\n // did, which caused a deadlock: when the first decode ran long (e.g., cold-start\n // fetch of a 50 MB blob), reportVideoTime captured a large stale-drift value, then\n // every subsequent tick skipped on drift, so reportVideoTime never ran again, and\n // drift was frozen at the stale value forever. The clock is the source of truth;\n // each render just targets its current value, which naturally catches up.\n if (_frameInFlight) {\n logStateChange('skip-inflight', time);\n return;\n }\n\n logStateChange('render', time);\n\n // Kick off the decode. A watchdog timer clears _frameInFlight so the loop can\n // recover if renderFrame never resolves. We attach .then directly (no Promise.race)\n // to preserve the microtask profile — emitFrameRendered fires on the same tick\n // that renderFrame resolves.\n _frameInFlight = true;\n const clockRef = _clock;\n const compositionRef = _composition;\n let timedOut = false;\n const timeoutId = setTimeout(() => {\n timedOut = true;\n _frameInFlight = false;\n console.warn(\n `[PlaybackEngine] decode timeout at t=${time.toFixed(3)}s (>${decodeTimeoutMs}ms) — skipping frame`,\n );\n }, decodeTimeoutMs);\n\n _frameRenderer.renderFrame(compositionRef, time).then(frame => {\n clearTimeout(timeoutId);\n if (timedOut) return;\n _frameInFlight = false;\n if (_state !== 'playing' || _clock !== clockRef) return;\n clockRef.reportVideoTime(time);\n emitFrameRendered(frame);\n }).catch(err => {\n clearTimeout(timeoutId);\n if (timedOut) return;\n _frameInFlight = false;\n console.error('[PlaybackEngine] render error:', err);\n });\n };\n\n _rafId = requestAnimationFrame(loop);\n }\n\n function stopRafLoop(): void {\n if (_rafId !== null) {\n cancelAnimationFrame(_rafId);\n _rafId = null;\n }\n _frameInFlight = false;\n }\n\n const engine: PlaybackEngine = {\n get state() { return _state; },\n\n get currentTime() {\n return _clock?.currentTime ?? 0;\n },\n\n get playbackRate() {\n return _clock?.playbackRate ?? _playbackRate;\n },\n set playbackRate(rate: number) {\n _playbackRate = rate;\n if (_clock) {\n _clock.playbackRate = rate;\n }\n if (_audioScheduler) {\n _audioScheduler.setPlaybackRate(rate);\n }\n },\n\n get loop() {\n return _clock?.loop ?? _loop;\n },\n set loop(l: { start: number; end: number } | null) {\n _loop = l;\n if (_clock) {\n _clock.loop = l;\n }\n },\n\n async load(composition: Composition, resolver: AssetResolver): Promise<void> {\n // Invalidate any pending paused-seek renders from previous session\n _seekId++;\n\n // If previously loaded, destroy old subsystems\n if (_state !== 'idle') {\n destroySubsystems();\n }\n\n setState('loading');\n\n try {\n // Create AudioContext\n _audioContext = new AudioContext();\n\n // Create subsystems\n _decoder = createMediaDecoder(resolver, _audioContext);\n _compositor = await createCompositor(\n composition.settings.width,\n composition.settings.height,\n compositorType,\n );\n _frameRenderer = createFrameRenderer(\n _decoder,\n _compositor,\n composition.settings.width,\n composition.settings.height,\n subtitleRenderer,\n );\n _clock = createMasterClock({\n audioContext: _audioContext,\n duration: composition.duration,\n frameRate: composition.settings.fps,\n });\n _audioScheduler = createAudioScheduler({ audioContext: _audioContext });\n\n // Apply pending settings\n _clock.playbackRate = _playbackRate;\n _clock.loop = _loop;\n\n _composition = composition;\n\n // Pre-load audio buffers from every clip on every audio OR video track.\n // Video clips carry their own embedded audio (MediaDecoder.decodeAudio\n // extracts it from the container), so they participate in the audio\n // graph just like standalone audio clips. Clips with no audio stream\n // (images, silent videos) throw — we swallow and skip so they simply\n // have no buffer loaded, which makes scheduleClip a no-op for them.\n const mediaClips = composition.tracks\n .filter(track => track.type === 'audio' || track.type === 'video')\n .flatMap(track => track.clips);\n\n for (const clip of mediaClips) {\n try {\n const audioBuffer = await _decoder.decodeAudio(clip.assetId);\n _audioScheduler.loadClip(clip.id, audioBuffer);\n } catch {\n // Clip has no audio (image asset, silent video, or decode failure).\n // Leave the clip without a buffer; audio-scheduler tolerates this.\n }\n }\n\n setState('ready');\n } catch (err) {\n // Clean up any partially-created subsystems\n destroySubsystems();\n setState('idle');\n throw err;\n }\n },\n\n play(): void {\n if (!_clock || !_audioScheduler || !_composition) {\n throw new Error('Cannot play: no composition loaded. Call load() first.');\n }\n if (_state === 'playing') return;\n\n // AudioContext may start in 'suspended' state because it was created\n // inside engine.load() after awaits, losing the user-gesture chain.\n // resume() works as long as ANY user gesture has occurred on the page —\n // clicking the play button is sufficient. Fire-and-forget: audio will\n // unmute as soon as resume settles, which in practice is <1 frame.\n if (_audioContext && _audioContext.state === 'suspended') {\n _audioContext.resume().catch((err) => {\n console.warn('[PlaybackEngine] AudioContext resume failed:', err);\n });\n }\n\n _clock.play();\n _audioScheduler.setPlaybackRate(_playbackRate);\n const clockRef = _clock;\n _audioScheduler.play(_clock.currentTime, _composition, () => clockRef.currentTime);\n setState('playing');\n startRafLoop();\n },\n\n pause(): void {\n if (_state !== 'playing') return;\n\n stopRafLoop();\n _clock?.pause();\n _audioScheduler?.pause();\n setState('paused');\n },\n\n seek(time: number): void {\n if (!_clock || !_audioScheduler || !_composition) {\n throw new Error('Cannot seek: no composition loaded. Call load() first.');\n }\n\n _clock.seek(time);\n // Use the clamped time from clock so audio/video stay in sync\n const clampedTime = _clock.currentTime;\n\n // Only reschedule audio when playing — seeking while paused should not produce sound\n if (_state === 'playing') {\n _audioScheduler.setPlaybackRate(_playbackRate);\n _audioScheduler.seek(clampedTime, _composition);\n }\n\n // If not playing, render the frame at the seeked position\n if (_state !== 'playing' && _frameRenderer) {\n const thisSeekId = ++_seekId;\n _frameRenderer.renderFrame(_composition, clampedTime).then(frame => {\n if (thisSeekId !== _seekId) return; // Stale render, discard\n emitFrameRendered(frame);\n emitTimeUpdate(clampedTime);\n }).catch(err => {\n console.error('[PlaybackEngine] seek render error:', err);\n });\n }\n },\n\n onStateChange(cb: (state: PlaybackState) => void): () => void {\n stateChangeListeners.add(cb);\n return () => { stateChangeListeners.delete(cb); };\n },\n\n onTimeUpdate(cb: (time: number) => void): () => void {\n timeUpdateListeners.add(cb);\n return () => { timeUpdateListeners.delete(cb); };\n },\n\n onFrameRendered(cb: (frame: RenderedFrame) => void): () => void {\n frameRenderedListeners.add(cb);\n return () => { frameRenderedListeners.delete(cb); };\n },\n\n destroy(): void {\n stopRafLoop();\n destroySubsystems();\n stateChangeListeners.clear();\n timeUpdateListeners.clear();\n frameRenderedListeners.clear();\n _playbackRate = 1;\n _loop = null;\n _state = 'idle';\n },\n };\n\n return engine;\n}\n","import type { OfflineAudioRenderer } from './types.js';\nimport type { Clip, Track } from '@pneuma-craft/timeline';\n\nexport function createOfflineAudioRenderer(): OfflineAudioRenderer {\n return {\n async render(composition, _resolver, decodeAudio) {\n const sampleRate = composition.settings.sampleRate ?? 48000;\n const channels = 2;\n const duration = composition.duration;\n const length = Math.ceil(duration * sampleRate);\n\n const offlineCtx = new OfflineAudioContext(channels, length, sampleRate);\n\n // Collect clips from every unmuted media track. Video clips contribute\n // their embedded audio exactly like standalone audio clips — this is\n // what keeps the exported audio in lockstep with what the preview\n // plays. Subtitle/other non-media tracks are skipped.\n const audioClips: Array<{ clip: Clip; track: Track }> = [];\n for (const track of composition.tracks) {\n if (track.type !== 'audio' && track.type !== 'video') continue;\n if (track.muted) continue;\n for (const clip of track.clips) {\n audioClips.push({ clip, track });\n }\n }\n\n // Schedule each clip\n for (const { clip, track } of audioClips) {\n let buffer: AudioBuffer;\n try {\n buffer = await decodeAudio(clip.assetId);\n } catch {\n // Clip has no audio stream (image asset, silent video) or decode\n // failed. Skip silently — video-track clips routinely have no\n // audio, so a warning here would be noise. True decode failures\n // on audio assets surface via the preview path instead.\n continue;\n }\n\n const source = offlineCtx.createBufferSource();\n source.buffer = buffer;\n\n const clipGain = offlineCtx.createGain();\n const clipVolume = clip.volume ?? 1;\n clipGain.gain.value = clipVolume;\n\n const trackGain = offlineCtx.createGain();\n trackGain.gain.value = track.volume;\n\n source.connect(clipGain);\n clipGain.connect(trackGain);\n trackGain.connect(offlineCtx.destination);\n\n // Fade automation\n const fadeIn = clip.fadeIn;\n const fadeOut = clip.fadeOut;\n\n if (fadeIn && fadeIn > 0) {\n clipGain.gain.setValueAtTime(0, clip.startTime);\n clipGain.gain.linearRampToValueAtTime(clipVolume, clip.startTime + fadeIn);\n }\n\n if (fadeOut && fadeOut > 0) {\n const fadeOutStart = clip.startTime + clip.duration - fadeOut;\n clipGain.gain.setValueAtTime(clipVolume, fadeOutStart);\n clipGain.gain.linearRampToValueAtTime(0, clip.startTime + clip.duration);\n }\n\n source.start(clip.startTime, clip.inPoint, clip.duration);\n }\n\n return offlineCtx.startRendering();\n },\n };\n}\n","import type { ExportEngine, ExportOptions, AssetResolver, SubtitleRenderer } from './types.js';\nimport type { Composition } from '@pneuma-craft/timeline';\nimport { createMediaDecoder } from './media-decoder.js';\nimport { createCompositor } from './compositor.js';\nimport { createFrameRenderer } from './frame-renderer.js';\nimport { createOfflineAudioRenderer } from './offline-audio-renderer.js';\nimport {\n Output,\n CanvasSource,\n AudioBufferSource,\n Mp4OutputFormat,\n WebMOutputFormat,\n BufferTarget,\n} from 'mediabunny';\n\nexport interface ExportEngineOptions {\n /**\n * Optional rasterizer for subtitle-track clips. Pass the same renderer used\n * by `createPlaybackEngine` so the exported video is pixel-identical to the\n * preview. When omitted, subtitle tracks are skipped during export.\n */\n subtitleRenderer?: SubtitleRenderer;\n}\n\nexport function createExportEngine(options?: ExportEngineOptions): ExportEngine {\n const subtitleRenderer = options?.subtitleRenderer;\n const progressListeners = new Set<(progress: number) => void>();\n let abortController: AbortController | null = null;\n\n function notifyProgress(progress: number): void {\n for (const cb of progressListeners) {\n try { cb(progress); } catch (e) { console.error('[ExportEngine]', e); }\n }\n }\n\n return {\n async export(\n composition: Composition,\n options: ExportOptions,\n resolver: AssetResolver,\n ): Promise<Blob> {\n abortController = new AbortController();\n const signal = abortController.signal;\n\n const fps = options.fps ?? composition.settings.fps;\n const width = options.width ?? composition.settings.width;\n const height = options.height ?? composition.settings.height;\n const totalFrames = Math.ceil(composition.duration * fps);\n\n // Minimal OfflineAudioContext used only for decodeAudioData during asset\n // decoding (export has its own offline context for actual audio rendering).\n const decoderAudioCtx = new OfflineAudioContext(1, 1, composition.settings.sampleRate || 48000);\n const decoder = createMediaDecoder(resolver, decoderAudioCtx);\n const compositor = await createCompositor(width, height, 'canvas2d');\n const renderer = createFrameRenderer(decoder, compositor, width, height, subtitleRenderer);\n\n try {\n const format = options.format === 'webm'\n ? new WebMOutputFormat()\n : new Mp4OutputFormat({ fastStart: 'in-memory' });\n\n const target = new BufferTarget();\n const output = new Output({ format, target });\n\n const renderCanvas = new OffscreenCanvas(width, height);\n const videoSource = new CanvasSource(renderCanvas, {\n codec: options.videoCodec,\n bitrate: options.videoBitrate,\n });\n\n const audioSource = new AudioBufferSource({\n codec: options.audioCodec,\n bitrate: options.audioBitrate,\n });\n\n output.addVideoTrack(videoSource);\n output.addAudioTrack(audioSource);\n await output.start();\n\n // Render video frames\n const renderCtx = renderCanvas.getContext('2d')!;\n\n for (let frame = 0; frame < totalFrames; frame++) {\n if (signal.aborted) throw new Error('Export aborted');\n\n const time = frame / fps;\n const rendered = await renderer.renderFrame(composition, time);\n\n renderCtx.clearRect(0, 0, width, height);\n renderCtx.drawImage(rendered.image, 0, 0, width, height);\n rendered.image.close();\n\n await videoSource.add(time, 1 / fps);\n\n if ((frame + 1) % 5 === 0 || frame === totalFrames - 1) {\n notifyProgress((frame + 1) / totalFrames);\n }\n\n // Yield to event loop periodically\n if ((frame + 1) % 5 === 0) {\n await new Promise(r => setTimeout(r, 0));\n }\n }\n\n videoSource.close();\n if (signal.aborted) throw new Error('Export aborted');\n\n // Render audio offline\n const offlineRenderer = createOfflineAudioRenderer();\n const audioBuffer = await offlineRenderer.render(\n composition,\n resolver,\n (assetId) => decoder.decodeAudio(assetId),\n );\n if (signal.aborted) throw new Error('Export aborted');\n\n await audioSource.add(audioBuffer);\n audioSource.close();\n if (signal.aborted) throw new Error('Export aborted');\n\n await output.finalize();\n\n const buffer = target.buffer;\n if (!buffer) throw new Error('Export produced no output');\n\n const mimeType = options.format === 'webm' ? 'video/webm' : 'video/mp4';\n return new Blob([buffer], { type: mimeType });\n } finally {\n renderer.destroy();\n abortController = null;\n }\n },\n\n onProgress(cb: (progress: number) => void): () => void {\n progressListeners.add(cb);\n return () => { progressListeners.delete(cb); };\n },\n\n abort(): void {\n abortController?.abort();\n },\n };\n}\n"],"mappings":"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","names":["import_mediabunny","options"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/media-decoder.ts","../src/canvas2d-compositor.ts","../src/gpu-compositor.ts","../src/compositor.ts","../src/frame-renderer.ts","../src/master-clock.ts","../src/audio-scheduler.ts","../src/playback-engine.ts","../src/offline-audio-renderer.ts","../src/export-engine.ts"],"sourcesContent":["// ── Types ──────────────────────────────────────────────────────────────\nexport type {\n AssetResolver,\n MediaInfo,\n MediaDecoder,\n CompositeLayer,\n Compositor,\n RenderedFrame,\n FrameRenderer,\n SubtitleRenderer,\n SubtitleRenderParams,\n ClockState,\n MasterClock,\n AudioScheduler,\n PlaybackState,\n PlaybackEngine,\n ExportOptions,\n ExportEngine,\n OfflineAudioRenderer,\n} from './types.js';\n\n// ── Creation Functions ─────────────────────────────────────────────────\nexport { createPlaybackEngine } from './playback-engine.js';\nexport type { PlaybackEngineOptions } from './playback-engine.js';\nexport { createExportEngine } from './export-engine.js';\nexport type { ExportEngineOptions } from './export-engine.js';\nexport { createFrameRenderer } from './frame-renderer.js';\nexport { createMasterClock } from './master-clock.js';\nexport type { MasterClockOptions } from './master-clock.js';\nexport { createAudioScheduler } from './audio-scheduler.js';\nexport type { AudioSchedulerOptions } from './audio-scheduler.js';\nexport { createMediaDecoder } from './media-decoder.js';\n\n// ── Compositor ─────────────────────────────────────────────────────────\nexport { createCompositor } from './compositor.js';\nexport type { CompositorType } from './compositor.js';\nexport { createCanvas2DCompositor } from './canvas2d-compositor.js';\nexport { createGPUCompositor } from './gpu-compositor.js';\n\n// ── Offline Audio ──────────────────────────────────────────────────────\nexport { createOfflineAudioRenderer } from './offline-audio-renderer.js';\n","import type { MediaDecoder, AssetResolver, MediaInfo } from './types.js';\nimport { Input, BlobSource, CanvasSink, AudioBufferSink, ALL_FORMATS } from 'mediabunny';\n\ninterface CachedAsset {\n input: InstanceType<typeof Input>;\n blob: Blob;\n videoSink: InstanceType<typeof CanvasSink> | null;\n imageBitmap: ImageBitmap | null;\n audioBuffer: AudioBuffer | null;\n mediaInfo: MediaInfo | null;\n}\n\n/**\n * Decodes an image blob and rasterizes it onto a (width × height) OffscreenCanvas\n * with \"contain\" fit (aspect-preserving scale, transparent letterbox). This matches\n * the CanvasSink's `fit: 'contain'` + `alpha: true` behavior so image layers and\n * video layers share the exact same output dimensions — important because the GPU\n * compositor copies external images into a texture of that exact size and rejects\n * mismatches. The `{ alpha: true }` context option preserves the source image's\n * alpha channel (e.g. a PNG with transparency) and leaves letterbox regions fully\n * transparent so underlying layers show through.\n */\nasync function decodeImageFitted(blob: Blob, width: number, height: number): Promise<ImageBitmap> {\n const raw = await createImageBitmap(blob);\n try {\n const canvas = new OffscreenCanvas(width, height);\n const ctx = canvas.getContext('2d', { alpha: true });\n if (!ctx) throw new Error('Failed to get 2d context for image resize');\n const scale = Math.min(width / raw.width, height / raw.height);\n const drawW = raw.width * scale;\n const drawH = raw.height * scale;\n const dx = (width - drawW) / 2;\n const dy = (height - drawH) / 2;\n ctx.drawImage(raw, 0, 0, raw.width, raw.height, dx, dy, drawW, drawH);\n return await createImageBitmap(canvas);\n } finally {\n raw.close();\n }\n}\n\nexport function createMediaDecoder(\n resolver: AssetResolver,\n audioContext: BaseAudioContext,\n): MediaDecoder {\n const cache = new Map<string, CachedAsset>();\n const initPromises = new Map<string, Promise<CachedAsset>>();\n\n async function getOrCreateAsset(assetId: string): Promise<CachedAsset> {\n const existing = cache.get(assetId);\n if (existing) return existing;\n\n const pending = initPromises.get(assetId);\n if (pending) return pending;\n\n const promise = (async () => {\n const blob = await resolver.fetchBlob(assetId);\n const input = new Input({ source: new BlobSource(blob), formats: ALL_FORMATS });\n const asset: CachedAsset = { input, blob, videoSink: null, imageBitmap: null, audioBuffer: null, mediaInfo: null };\n cache.set(assetId, asset);\n initPromises.delete(assetId);\n return asset;\n })();\n\n // Remove from initPromises on failure so future attempts can retry\n promise.catch(() => {\n initPromises.delete(assetId);\n });\n\n initPromises.set(assetId, promise);\n return promise;\n }\n\n return {\n async decodeVideoFrame(assetId, time, width, height) {\n const asset = await getOrCreateAsset(assetId);\n\n // Image fast path — cached ImageBitmap (already fitted to the target\n // dimensions), timestamp ignored. An image clip behaves as a static\n // frame that occupies its Clip's full duration.\n if (asset.imageBitmap) return asset.imageBitmap;\n\n if (!asset.videoSink) {\n let videoTrack: Awaited<ReturnType<typeof asset.input.getPrimaryVideoTrack>> | null = null;\n try {\n videoTrack = await asset.input.getPrimaryVideoTrack();\n } catch {\n // MediaBunny couldn't parse this blob as a media container — likely\n // a standalone image (png/jpg/webp/gif). Fall through to ImageBitmap.\n }\n if (!videoTrack) {\n try {\n asset.imageBitmap = await decodeImageFitted(asset.blob, width, height);\n return asset.imageBitmap;\n } catch (err) {\n throw new Error(\n `Asset ${assetId} has no video track and is not a decodable image: ${(err as Error).message}`,\n );\n }\n }\n asset.videoSink = new CanvasSink(videoTrack, {\n width,\n height,\n fit: 'contain',\n poolSize: 5,\n // Preserve source alpha — required for transparent videos (e.g. WebM\n // with VP8/VP9 alpha, HEVC/AV1 with alpha). With `alpha: false`\n // (mediabunny default) CanvasSink flattens transparent pixels to a\n // black background, breaking compositing of alpha videos.\n alpha: true,\n });\n }\n const result = await asset.videoSink.getCanvas(time);\n if (!result) throw new Error(`Failed to decode frame at ${time}s for asset ${assetId}`);\n return result.canvas;\n },\n\n async decodeAudio(assetId) {\n const asset = await getOrCreateAsset(assetId);\n if (asset.audioBuffer) return asset.audioBuffer;\n\n // Fast path: try browser-native decodeAudioData on the whole blob.\n // This works for standalone audio files (mp3/wav/flac/ogg/m4a) and is\n // much simpler and faster than iterating through MediaBunny packets.\n try {\n const arrayBuffer = await asset.blob.arrayBuffer();\n // decodeAudioData detaches the buffer, so pass a copy (slice(0) is cheap).\n const buffer = await audioContext.decodeAudioData(arrayBuffer.slice(0));\n asset.audioBuffer = buffer;\n return buffer;\n } catch {\n // Fall through to MediaBunny path — typical for video containers whose\n // audio track the browser's decodeAudioData can't extract directly.\n }\n\n const audioTrack = await asset.input.getPrimaryAudioTrack();\n if (!audioTrack) throw new Error(`No audio track in asset ${assetId}`);\n\n // Iterate the full track via AudioBufferSink.buffers() and concatenate\n // into one AudioBuffer. getBuffer(0) alone only returns the first packet.\n const sink = new AudioBufferSink(audioTrack);\n const chunks: AudioBuffer[] = [];\n let totalFrames = 0;\n let sampleRate = 0;\n let numberOfChannels = 0;\n for await (const wrapped of sink.buffers()) {\n const buf = wrapped.buffer;\n chunks.push(buf);\n totalFrames += buf.length;\n if (!sampleRate) sampleRate = buf.sampleRate;\n if (!numberOfChannels) numberOfChannels = buf.numberOfChannels;\n }\n if (chunks.length === 0 || totalFrames === 0) {\n throw new Error(`Failed to decode audio for asset ${assetId}`);\n }\n\n const merged = audioContext.createBuffer(numberOfChannels, totalFrames, sampleRate);\n let offset = 0;\n for (const chunk of chunks) {\n for (let ch = 0; ch < numberOfChannels; ch++) {\n const src = chunk.getChannelData(Math.min(ch, chunk.numberOfChannels - 1));\n merged.copyToChannel(src, ch, offset);\n }\n offset += chunk.length;\n }\n asset.audioBuffer = merged;\n return merged;\n },\n\n async getMediaInfo(assetId) {\n const asset = await getOrCreateAsset(assetId);\n if (asset.mediaInfo) return asset.mediaInfo;\n const videoTrack = await asset.input.getPrimaryVideoTrack();\n const audioTrack = await asset.input.getPrimaryAudioTrack();\n const duration = await asset.input.computeDuration();\n let fps = 0;\n if (videoTrack) {\n const stats = await videoTrack.computePacketStats(100);\n fps = stats.averagePacketRate;\n }\n const info: MediaInfo = {\n duration,\n width: videoTrack?.displayWidth ?? 0,\n height: videoTrack?.displayHeight ?? 0,\n fps,\n hasVideo: videoTrack !== null,\n hasAudio: audioTrack !== null,\n videoCodec: videoTrack?.codec ?? null,\n audioCodec: audioTrack?.codec ?? null,\n sampleRate: audioTrack?.sampleRate ?? 0,\n channels: audioTrack?.numberOfChannels ?? 0,\n };\n asset.mediaInfo = info;\n return info;\n },\n\n destroy() {\n for (const asset of cache.values()) {\n asset.input.dispose();\n asset.imageBitmap?.close();\n }\n cache.clear();\n initPromises.clear();\n },\n };\n}\n","import type { Compositor, CompositeLayer } from './types.js';\n\nexport function createCanvas2DCompositor(width: number, height: number): Compositor {\n // `{ alpha: true }` keeps the backing buffer's alpha channel, so transparent\n // pixels from source layers (alpha videos, PNGs, letterboxed content) remain\n // transparent in the composed ImageBitmap instead of being flattened to black.\n let canvas = new OffscreenCanvas(width, height);\n let ctx = canvas.getContext('2d', { alpha: true })!;\n let currentWidth = width;\n let currentHeight = height;\n\n return {\n async composite(layers: CompositeLayer[]): Promise<ImageBitmap> {\n ctx.clearRect(0, 0, currentWidth, currentHeight);\n\n // Sort by zIndex ascending (low = bottom, high = top)\n const sorted = [...layers].sort((a, b) => a.zIndex - b.zIndex);\n\n for (const layer of sorted) {\n if (layer.opacity <= 0) continue;\n\n ctx.globalAlpha = layer.opacity;\n ctx.drawImage(layer.source, 0, 0, currentWidth, currentHeight);\n }\n\n ctx.globalAlpha = 1;\n return createImageBitmap(canvas);\n },\n\n resize(w: number, h: number): void {\n currentWidth = w;\n currentHeight = h;\n canvas = new OffscreenCanvas(w, h);\n ctx = canvas.getContext('2d', { alpha: true })!;\n },\n\n destroy(): void {\n // OffscreenCanvas has no explicit cleanup needed\n },\n };\n}\n","import type { Compositor, CompositeLayer } from './types.js';\n\nconst VERTEX_SHADER = /* wgsl */ `\nstruct VertexOutput {\n @builtin(position) position: vec4f,\n @location(0) texCoord: vec2f,\n};\n\n@vertex\nfn main(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {\n // Full-screen quad: 6 vertices, 2 triangles\n var positions = array<vec2f, 6>(\n vec2f(-1.0, -1.0),\n vec2f( 1.0, -1.0),\n vec2f(-1.0, 1.0),\n vec2f(-1.0, 1.0),\n vec2f( 1.0, -1.0),\n vec2f( 1.0, 1.0),\n );\n\n var texCoords = array<vec2f, 6>(\n vec2f(0.0, 1.0),\n vec2f(1.0, 1.0),\n vec2f(0.0, 0.0),\n vec2f(0.0, 0.0),\n vec2f(1.0, 1.0),\n vec2f(1.0, 0.0),\n );\n\n var output: VertexOutput;\n output.position = vec4f(positions[vertexIndex], 0.0, 1.0);\n output.texCoord = texCoords[vertexIndex];\n return output;\n}\n`;\n\nconst FRAGMENT_SHADER = /* wgsl */ `\n@group(0) @binding(0) var texSampler: sampler;\n@group(0) @binding(1) var texSource: texture_2d<f32>;\n@group(0) @binding(2) var<uniform> opacity: f32;\n\n@fragment\nfn main(@location(0) texCoord: vec2f) -> @location(0) vec4f {\n var color = textureSample(texSource, texSampler, texCoord);\n // Multiply alpha by uniform opacity\n color.a = color.a * opacity;\n // Premultiply RGB by the new alpha\n color = vec4f(color.rgb * color.a, color.a);\n return color;\n}\n`;\n\nexport async function createGPUCompositor(width: number, height: number): Promise<Compositor> {\n if (!navigator.gpu) {\n throw new Error('WebGPU not available');\n }\n const adapter = await navigator.gpu.requestAdapter();\n if (!adapter) {\n throw new Error('Failed to get WebGPU adapter');\n }\n\n const device = await adapter.requestDevice();\n\n // Create sampler\n const sampler = device.createSampler({\n magFilter: 'linear',\n minFilter: 'linear',\n });\n\n // Create uniform buffer for opacity (f32 = 4 bytes, aligned to 16)\n const opacityBuffer = device.createBuffer({\n size: 16,\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n });\n\n // Create bind group layout\n const bindGroupLayout = device.createBindGroupLayout({\n entries: [\n { binding: 0, visibility: GPUShaderStage.FRAGMENT, sampler: {} },\n { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: {} },\n { binding: 2, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } },\n ],\n });\n\n const pipelineLayout = device.createPipelineLayout({\n bindGroupLayouts: [bindGroupLayout],\n });\n\n // Create shader modules\n const vertexModule = device.createShaderModule({ code: VERTEX_SHADER });\n const fragmentModule = device.createShaderModule({ code: FRAGMENT_SHADER });\n\n // Create render pipeline with premultiplied alpha blending\n const pipeline = device.createRenderPipeline({\n layout: pipelineLayout,\n vertex: {\n module: vertexModule,\n entryPoint: 'main',\n },\n fragment: {\n module: fragmentModule,\n entryPoint: 'main',\n targets: [{\n format: 'rgba8unorm',\n blend: {\n color: {\n srcFactor: 'one',\n dstFactor: 'one-minus-src-alpha',\n operation: 'add',\n },\n alpha: {\n srcFactor: 'one',\n dstFactor: 'one-minus-src-alpha',\n operation: 'add',\n },\n },\n }],\n },\n primitive: {\n topology: 'triangle-list',\n },\n });\n\n // Create output texture\n let currentWidth = width;\n let currentHeight = height;\n let outputTexture = createOutputTexture(device, width, height);\n\n function createOutputTexture(dev: GPUDevice, w: number, h: number): GPUTexture {\n return dev.createTexture({\n size: { width: w, height: h },\n format: 'rgba8unorm',\n usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC | GPUTextureUsage.TEXTURE_BINDING,\n });\n }\n\n return {\n async composite(layers: CompositeLayer[]): Promise<ImageBitmap> {\n const sorted = [...layers]\n .filter((l) => l.opacity > 0)\n .sort((a, b) => a.zIndex - b.zIndex);\n\n const encoder = device.createCommandEncoder();\n const outputView = outputTexture.createView();\n\n const layerTextures: GPUTexture[] = [];\n\n if (sorted.length === 0) {\n // Clear pass only\n const pass = encoder.beginRenderPass({\n colorAttachments: [{\n view: outputView,\n loadOp: 'clear',\n storeOp: 'store',\n clearValue: { r: 0, g: 0, b: 0, a: 0 },\n }],\n });\n pass.end();\n } else {\n for (let i = 0; i < sorted.length; i++) {\n const layer = sorted[i];\n\n // Create texture from source\n const layerTexture = device.createTexture({\n size: { width: currentWidth, height: currentHeight },\n format: 'rgba8unorm',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,\n });\n layerTextures.push(layerTexture);\n\n device.queue.copyExternalImageToTexture(\n { source: layer.source as ImageBitmap },\n { texture: layerTexture },\n { width: currentWidth, height: currentHeight },\n );\n\n // Write opacity uniform\n const opacityData = new Float32Array([layer.opacity]);\n device.queue.writeBuffer(opacityBuffer, 0, opacityData);\n\n // Create bind group for this layer\n const bindGroup = device.createBindGroup({\n layout: bindGroupLayout,\n entries: [\n { binding: 0, resource: sampler },\n { binding: 1, resource: layerTexture.createView() },\n { binding: 2, resource: { buffer: opacityBuffer } },\n ],\n });\n\n // Render pass: first pass clears, subsequent passes load\n const loadOp: 'load' | 'clear' = i === 0 ? 'clear' : 'load';\n const pass = encoder.beginRenderPass({\n colorAttachments: [{\n view: outputView,\n loadOp,\n storeOp: 'store',\n ...(loadOp === 'clear' ? { clearValue: { r: 0, g: 0, b: 0, a: 0 } } : {}),\n }],\n });\n\n pass.setPipeline(pipeline);\n pass.setBindGroup(0, bindGroup);\n pass.draw(6);\n pass.end();\n }\n }\n\n // Copy output texture to staging buffer for CPU readback\n const bytesPerRow = Math.ceil(currentWidth * 4 / 256) * 256;\n const bufferSize = bytesPerRow * currentHeight;\n const stagingBuffer = device.createBuffer({\n size: bufferSize,\n usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,\n });\n\n encoder.copyTextureToBuffer(\n { texture: outputTexture },\n { buffer: stagingBuffer, bytesPerRow },\n { width: currentWidth, height: currentHeight },\n );\n\n device.queue.submit([encoder.finish()]);\n\n // Destroy layer textures after command submission\n for (const tex of layerTextures) {\n tex.destroy();\n }\n\n // Read back pixels from staging buffer\n await stagingBuffer.mapAsync(GPUMapMode.READ);\n const rawData = stagingBuffer.getMappedRange();\n\n // Copy pixel data row by row (bytesPerRow may have padding)\n const pixelData = new Uint8ClampedArray(currentWidth * currentHeight * 4);\n const src = new Uint8Array(rawData);\n for (let row = 0; row < currentHeight; row++) {\n const srcOffset = row * bytesPerRow;\n const dstOffset = row * currentWidth * 4;\n pixelData.set(src.subarray(srcOffset, srcOffset + currentWidth * 4), dstOffset);\n }\n\n stagingBuffer.unmap();\n stagingBuffer.destroy();\n\n // The fragment shader outputs premultiplied RGB (see FRAGMENT_SHADER),\n // so `pixelData` is in premultiplied form. `putImageData` treats its\n // input as straight (unpremultiplied) alpha — feeding premultiplied\n // bytes in would cause the canvas to double-premultiply at semi-\n // transparent pixels, producing darkened/wrong colors on alpha-blended\n // edges. Unpremultiply so the straight-alpha ImageData contract holds.\n for (let i = 0; i < pixelData.length; i += 4) {\n const a = pixelData[i + 3];\n if (a > 0 && a < 255) {\n const inv = 255 / a;\n pixelData[i] = Math.min(255, Math.round(pixelData[i] * inv));\n pixelData[i + 1] = Math.min(255, Math.round(pixelData[i + 1] * inv));\n pixelData[i + 2] = Math.min(255, Math.round(pixelData[i + 2] * inv));\n }\n }\n\n // `{ alpha: true }` keeps the readback canvas's alpha channel so a fully\n // transparent output texture stays transparent (default `alpha: false`\n // would flatten it to opaque black).\n const readbackCanvas = new OffscreenCanvas(currentWidth, currentHeight);\n const ctx = readbackCanvas.getContext('2d', { alpha: true })!;\n const imageData = new ImageData(pixelData, currentWidth, currentHeight);\n ctx.putImageData(imageData, 0, 0);\n return createImageBitmap(readbackCanvas);\n },\n\n resize(w: number, h: number): void {\n outputTexture.destroy();\n currentWidth = w;\n currentHeight = h;\n outputTexture = createOutputTexture(device, w, h);\n },\n\n destroy(): void {\n outputTexture.destroy();\n opacityBuffer.destroy();\n device.destroy();\n },\n };\n}\n","import type { Compositor } from './types.js';\nimport { createCanvas2DCompositor } from './canvas2d-compositor.js';\nimport { createGPUCompositor } from './gpu-compositor.js';\n\nexport type CompositorType = 'gpu' | 'canvas2d' | 'auto';\n\nexport async function createCompositor(\n width: number,\n height: number,\n type: CompositorType = 'auto',\n): Promise<Compositor> {\n if (type === 'canvas2d') {\n return createCanvas2DCompositor(width, height);\n }\n\n if (type === 'gpu' || type === 'auto') {\n if (typeof navigator !== 'undefined' && (navigator as unknown as { gpu?: unknown }).gpu) {\n try {\n return await createGPUCompositor(width, height);\n } catch {\n if (type === 'gpu') {\n throw new Error('WebGPU compositor requested but initialization failed');\n }\n }\n } else if (type === 'gpu') {\n throw new Error('WebGPU not available');\n }\n }\n\n return createCanvas2DCompositor(width, height);\n}\n","import type {\n FrameRenderer,\n RenderedFrame,\n MediaDecoder,\n Compositor,\n CompositeLayer,\n SubtitleRenderer,\n} from './types.js';\nimport type { Composition, ResolvedClip, ResolvedPreviewFrame } from '@pneuma-craft/timeline';\nimport { resolveFrame } from '@pneuma-craft/timeline';\n\nexport interface CreateFrameRendererOptions {\n readonly subtitleRenderer?: SubtitleRenderer;\n /**\n * Render preview frames in tracks where no real clip covers the current\n * time. Defaults to false — preview frames are a planning-layer visual\n * that should NOT appear in final exports unless explicitly requested.\n * Engines wire their own defaults: PlaybackEngine passes true, ExportEngine\n * passes false.\n */\n readonly includePreviewFrames?: boolean;\n}\n\n// Accept either the legacy positional SubtitleRenderer (pre-preview-frame\n// callers) or the new options object. Internal callers (playback-engine,\n// export-engine) and tests have been migrated to the options form.\ntype LegacyArg = SubtitleRenderer | CreateFrameRendererOptions | undefined;\n\nfunction normalizeOptions(arg: LegacyArg): CreateFrameRendererOptions {\n if (arg === undefined) return {};\n if (typeof arg === 'function') return { subtitleRenderer: arg };\n return arg;\n}\n\nexport function createFrameRenderer(\n decoder: MediaDecoder,\n compositor: Compositor,\n width: number,\n height: number,\n optionsOrLegacySubtitleRenderer?: LegacyArg,\n): FrameRenderer {\n const options = normalizeOptions(optionsOrLegacySubtitleRenderer);\n const { subtitleRenderer, includePreviewFrames = false } = options;\n\n return {\n async renderFrame(composition: Composition, time: number): Promise<RenderedFrame> {\n const resolved = resolveFrame(composition, time);\n\n // Bucket per track for O(1) lookup. resolveFrame guarantees clips and\n // previewFrames are mutually exclusive per track, so we walk\n // composition.tracks once and pick at most one bucket per track.\n const clipsByTrack = new Map<string, ResolvedClip[]>();\n for (const rc of resolved.clips) {\n const arr = clipsByTrack.get(rc.track.id);\n if (arr) arr.push(rc); else clipsByTrack.set(rc.track.id, [rc]);\n }\n const previewByTrack = new Map<string, ResolvedPreviewFrame>();\n for (const rpf of resolved.previewFrames) previewByTrack.set(rpf.track.id, rpf);\n\n const layers: CompositeLayer[] = [];\n let zIndex = 0;\n\n // Video layers — iterate tracks in array order so z-stack matches\n // composition.tracks. Per-track decision: clip wins; preview is the\n // fallback only when allowed by the renderer's includePreviewFrames flag.\n for (const track of composition.tracks) {\n if (track.type !== 'video') continue;\n\n const trackClips = clipsByTrack.get(track.id);\n if (trackClips && trackClips.length > 0) {\n for (const rc of trackClips) {\n const source = await decoder.decodeVideoFrame(\n rc.clip.assetId, rc.localTime, width, height,\n );\n layers.push({ source, opacity: 1, zIndex: zIndex++ });\n }\n continue;\n }\n\n if (includePreviewFrames) {\n const rpf = previewByTrack.get(track.id);\n if (rpf) {\n // Image asset: decoder.decodeVideoFrame uses the cached-ImageBitmap\n // fast path and ignores the time argument.\n const source = await decoder.decodeVideoFrame(\n rpf.previewFrame.assetId, 0, width, height,\n );\n layers.push({ source, opacity: 1, zIndex: zIndex++ });\n }\n }\n }\n\n // Subtitles always composite on top of every video / preview layer,\n // regardless of track ordering in the composition. Track ordering\n // between multiple subtitle tracks is preserved.\n if (subtitleRenderer) {\n const subtitleClips = resolved.clips.filter(rc => rc.track.type === 'subtitle');\n for (const rc of subtitleClips) {\n const rendered = await subtitleRenderer({\n clip: rc.clip,\n localTime: rc.localTime,\n width,\n height,\n });\n if (rendered) {\n layers.push({ source: rendered, opacity: 1, zIndex: zIndex++ });\n }\n }\n }\n\n const image = await compositor.composite(layers);\n\n return { image, time, width, height };\n },\n\n destroy(): void {\n decoder.destroy();\n compositor.destroy();\n },\n };\n}\n","import type { MasterClock, ClockState } from './types.js';\n\nexport interface MasterClockOptions {\n audioContext: AudioContext;\n duration?: number;\n frameRate?: number;\n}\n\nexport function createMasterClock(options: MasterClockOptions): MasterClock {\n const audioContext = options.audioContext;\n\n let state: ClockState = 'stopped';\n let _duration = options.duration ?? 0;\n let _playbackRate = 1;\n let _loop: { start: number; end: number } | null = null;\n\n // Timing anchors\n let startAudioContextTime = 0;\n let startTimelineTime = 0;\n let pausedAt = 0;\n\n // Drift tracking\n let _driftMs = 0;\n\n // Subscribers\n const timeUpdateListeners = new Set<(time: number) => void>();\n const stateChangeListeners = new Set<(state: ClockState) => void>();\n\n function computeCurrentTime(): number {\n if (state !== 'playing') return pausedAt;\n\n const elapsed = (audioContext.currentTime - startAudioContextTime) * _playbackRate;\n let time = startTimelineTime + elapsed;\n\n if (_loop && _loop.end > _loop.start && time >= _loop.end) {\n const loopDuration = _loop.end - _loop.start;\n time = _loop.start + ((time - _loop.start) % loopDuration);\n }\n\n return Math.max(0, Math.min(time, _duration || Infinity));\n }\n\n function notifyTimeUpdate(time: number): void {\n for (const cb of timeUpdateListeners) {\n try { cb(time); } catch (e) { console.error('[MasterClock] listener error:', e); }\n }\n }\n\n function notifyStateChange(newState: ClockState): void {\n for (const cb of stateChangeListeners) {\n try { cb(newState); } catch (e) { console.error('[MasterClock] listener error:', e); }\n }\n }\n\n function setState(newState: ClockState): void {\n if (state === newState) return;\n state = newState;\n notifyStateChange(newState);\n }\n\n const clock: MasterClock = {\n get currentTime() { return computeCurrentTime(); },\n get state() { return state; },\n get driftMs() { return _driftMs; },\n\n get playbackRate() { return _playbackRate; },\n set playbackRate(rate: number) {\n const clamped = Math.max(0.1, Math.min(rate, 16));\n if (state === 'playing') {\n const current = computeCurrentTime();\n startTimelineTime = current;\n startAudioContextTime = audioContext.currentTime;\n }\n _playbackRate = clamped;\n },\n\n get duration() { return _duration; },\n set duration(d: number) { _duration = Math.max(0, d); },\n\n get loop() { return _loop; },\n set loop(l) { _loop = l; },\n\n play(): void {\n if (state === 'playing') return;\n startTimelineTime = pausedAt;\n startAudioContextTime = audioContext.currentTime;\n setState('playing');\n if (audioContext.state === 'suspended') {\n audioContext.resume();\n }\n },\n\n pause(): void {\n if (state !== 'playing') return;\n pausedAt = computeCurrentTime();\n setState('paused');\n },\n\n seek(time: number): void {\n const clamped = Math.max(0, Math.min(time, _duration));\n if (state === 'playing') {\n startTimelineTime = clamped;\n startAudioContextTime = audioContext.currentTime;\n } else {\n pausedAt = clamped;\n }\n notifyTimeUpdate(clamped);\n },\n\n reportVideoTime(videoTime: number): void {\n const audioTime = computeCurrentTime();\n _driftMs = (audioTime - videoTime) * 1000;\n },\n\n onTimeUpdate(cb) {\n timeUpdateListeners.add(cb);\n return () => { timeUpdateListeners.delete(cb); };\n },\n\n onStateChange(cb) {\n stateChangeListeners.add(cb);\n return () => { stateChangeListeners.delete(cb); };\n },\n\n destroy(): void {\n if (state === 'playing') {\n pausedAt = computeCurrentTime();\n }\n state = 'stopped';\n timeUpdateListeners.clear();\n stateChangeListeners.clear();\n _driftMs = 0;\n },\n };\n\n return clock;\n}\n","import type { AudioScheduler } from './types.js';\nimport type { Composition, Clip, Track } from '@pneuma-craft/timeline';\n\nexport interface AudioSchedulerOptions {\n audioContext: AudioContext;\n}\n\ninterface ActiveSource {\n source: AudioBufferSourceNode;\n clipGain: GainNode;\n}\n\nexport function createAudioScheduler(options: AudioSchedulerOptions): AudioScheduler {\n const { audioContext } = options;\n\n // Buffer storage: clipId → AudioBuffer\n const clipBuffers = new Map<string, AudioBuffer>();\n\n // Per-track gain nodes: trackId → GainNode\n const trackGains = new Map<string, GainNode>();\n\n // Per-track volume (separate from mute): trackId → volume\n const trackVolumes = new Map<string, number>();\n\n // Per-track muted state: trackId → muted\n const trackMutedStates = new Map<string, boolean>();\n\n // Currently active sources\n let activeSources: ActiveSource[] = [];\n\n // Scheduler tick state for future clip scheduling\n let schedulerIntervalId: ReturnType<typeof setInterval> | null = null;\n let scheduledClipIds = new Set<string>();\n let currentComposition: Composition | null = null;\n\n // Playback rate (applied to elapsed time and source nodes)\n let _playbackRate = 1;\n\n // Whether playback is currently active\n let _isPlaying = false;\n\n // Previous timeline time for loop-wrap detection\n let _prevTimelineTime = 0;\n\n // Callback to get current timeline time from the master clock (loop-aware)\n let _getCurrentTime: (() => number) | null = null;\n\n // Look-ahead window in seconds\n const LOOK_AHEAD = 0.2;\n\n // Master gain node connected to destination\n const masterGain = audioContext.createGain();\n masterGain.gain.value = 1;\n masterGain.connect(audioContext.destination);\n\n function getOrCreateTrackGain(track: Track): GainNode {\n let node = trackGains.get(track.id);\n if (!node) {\n node = audioContext.createGain();\n // Use runtime overrides if they exist, otherwise fall back to composition data\n const vol = trackVolumes.get(track.id) ?? track.volume ?? 1;\n const muted = trackMutedStates.get(track.id) ?? track.muted;\n node.gain.value = muted ? 0 : vol;\n trackVolumes.set(track.id, vol);\n node.connect(masterGain);\n trackGains.set(track.id, node);\n }\n return node;\n }\n\n function clearSchedulerTick(): void {\n if (schedulerIntervalId !== null) {\n clearInterval(schedulerIntervalId);\n schedulerIntervalId = null;\n }\n }\n\n function stopAllSources(): void {\n for (const { source } of activeSources) {\n try {\n source.stop();\n } catch {\n // Source may already be stopped\n }\n source.disconnect();\n }\n activeSources = [];\n }\n\n function isClipActiveAt(clip: Clip, fromTime: number): boolean {\n return clip.startTime <= fromTime && fromTime < clip.startTime + clip.duration;\n }\n\n function scheduleClip(clip: Clip, track: Track, fromTime: number): void {\n const buffer = clipBuffers.get(clip.id);\n if (!buffer) return;\n\n const trackGain = getOrCreateTrackGain(track);\n\n // Create per-clip gain node\n const clipGain = audioContext.createGain();\n const clipVolume = clip.volume ?? 1;\n clipGain.gain.value = clipVolume;\n\n // Create source node\n const source = audioContext.createBufferSource();\n source.buffer = buffer;\n\n // Apply playback rate to source node\n source.playbackRate.value = _playbackRate;\n\n // Connect: source → clipGain → trackGain → masterGain → destination\n source.connect(clipGain);\n clipGain.connect(trackGain);\n\n const now = audioContext.currentTime;\n\n // Determine if the clip starts in the future relative to current timeline time\n let contextStartTime: number;\n let sourceOffset: number;\n let remainingDuration: number;\n\n if (clip.startTime > fromTime) {\n // Future clip: schedule it to start at the correct AudioContext time\n // Divide by playbackRate: at 2x rate, 1 timeline second = 0.5 real seconds\n const timeUntilClipStart = (clip.startTime - fromTime) / _playbackRate;\n contextStartTime = now + timeUntilClipStart;\n sourceOffset = clip.inPoint;\n remainingDuration = clip.duration;\n } else {\n // Already active clip: start immediately with offset\n contextStartTime = 0;\n const elapsed = fromTime - clip.startTime;\n sourceOffset = clip.inPoint + elapsed;\n remainingDuration = clip.duration - elapsed;\n }\n\n // Apply fade automations\n // Fade durations must be divided by playbackRate since AudioContext runs in real time\n if (clip.fadeIn !== undefined && clip.fadeIn > 0) {\n const realFadeIn = clip.fadeIn / _playbackRate;\n if (clip.startTime > fromTime) {\n // Future clip: anchor fade to contextStartTime\n clipGain.gain.setValueAtTime(0, contextStartTime);\n clipGain.gain.linearRampToValueAtTime(clipVolume, contextStartTime + realFadeIn);\n } else {\n const fadeInEnd = clip.startTime + clip.fadeIn;\n if (fromTime < fadeInEnd) {\n // Still within fade-in region\n const fadeElapsed = fromTime - clip.startTime;\n const currentLevel = fadeElapsed > 0 ? (fadeElapsed / clip.fadeIn) * clipVolume : 0;\n const fadeRemaining = (clip.fadeIn - fadeElapsed) / _playbackRate;\n clipGain.gain.setValueAtTime(currentLevel, now);\n clipGain.gain.linearRampToValueAtTime(clipVolume, now + fadeRemaining);\n }\n }\n }\n\n if (clip.fadeOut !== undefined && clip.fadeOut > 0) {\n const realFadeOut = clip.fadeOut / _playbackRate;\n const realDuration = clip.duration / _playbackRate;\n if (clip.startTime > fromTime) {\n // Future clip: anchor fade to contextStartTime\n const fadeOutStart = contextStartTime + realDuration - realFadeOut;\n clipGain.gain.setValueAtTime(clipVolume, fadeOutStart);\n clipGain.gain.linearRampToValueAtTime(0, contextStartTime + realDuration);\n } else {\n const fadeOutStart = clip.startTime + clip.duration - clip.fadeOut;\n const fadeOutContextTime = now + Math.max(0, (fadeOutStart - fromTime) / _playbackRate);\n clipGain.gain.setValueAtTime(clipVolume, fadeOutContextTime);\n clipGain.gain.linearRampToValueAtTime(0, fadeOutContextTime + realFadeOut);\n }\n }\n\n source.start(contextStartTime, sourceOffset, remainingDuration);\n\n activeSources.push({ source, clipGain });\n }\n\n function isClipInLookAhead(clip: Clip, currentTime: number, lookAhead: number): boolean {\n const clipEnd = clip.startTime + clip.duration;\n // Clip starts within the look-ahead window and hasn't ended\n return clip.startTime > currentTime && clip.startTime <= currentTime + lookAhead && clipEnd > currentTime;\n }\n\n function scheduleComposition(fromTime: number, composition: Composition): void {\n for (const track of composition.tracks) {\n // Both audio tracks and video tracks contribute audio — a video clip's\n // embedded audio track gets scheduled alongside pure audio clips. Skip\n // anything that isn't a media track (e.g. subtitle) and anything that\n // the user has muted. Clips whose asset has no audio track never get a\n // buffer loaded (see loadClip in playback-engine.ts), so `scheduleClip`\n // naturally skips them.\n if (track.type !== 'audio' && track.type !== 'video') continue;\n if (track.muted) continue;\n\n for (const clip of track.clips) {\n if (!isClipActiveAt(clip, fromTime)) continue;\n if (scheduledClipIds.has(clip.id)) continue;\n scheduledClipIds.add(clip.id);\n scheduleClip(clip, track, fromTime);\n }\n }\n }\n\n function startSchedulerTick(fromTime: number, composition: Composition): void {\n clearSchedulerTick();\n currentComposition = composition;\n\n // Fallback values for computing timeline time when no getCurrentTime callback\n const tickStartContextTime = audioContext.currentTime;\n const tickStartFromTime = fromTime;\n\n schedulerIntervalId = setInterval(() => {\n if (!currentComposition) return;\n\n // Use clock's getCurrentTime if available (loop-aware), otherwise fallback\n let currentTimelineTime: number;\n if (_getCurrentTime) {\n currentTimelineTime = _getCurrentTime();\n } else {\n const elapsed = (audioContext.currentTime - tickStartContextTime) * _playbackRate;\n currentTimelineTime = tickStartFromTime + elapsed;\n }\n\n // Detect loop wrap: timeline time went backwards\n if (currentTimelineTime < _prevTimelineTime) {\n scheduledClipIds.clear();\n stopAllSources();\n }\n _prevTimelineTime = currentTimelineTime;\n\n for (const track of currentComposition.tracks) {\n if (track.type !== 'audio') continue;\n if (track.muted) continue;\n\n for (const clip of track.clips) {\n if (scheduledClipIds.has(clip.id)) continue;\n if (isClipInLookAhead(clip, currentTimelineTime, LOOK_AHEAD) || isClipActiveAt(clip, currentTimelineTime)) {\n scheduledClipIds.add(clip.id);\n scheduleClip(clip, track, currentTimelineTime);\n }\n }\n }\n }, 100);\n }\n\n const scheduler: AudioScheduler = {\n get audioContext() {\n return audioContext;\n },\n\n loadClip(clipId: string, audioBuffer: AudioBuffer): void {\n clipBuffers.set(clipId, audioBuffer);\n },\n\n play(fromTime: number, composition: Composition, getCurrentTime?: () => number): void {\n stopAllSources();\n clearSchedulerTick();\n scheduledClipIds = new Set();\n _prevTimelineTime = fromTime;\n _getCurrentTime = getCurrentTime ?? null;\n _isPlaying = true;\n currentComposition = composition;\n scheduleComposition(fromTime, composition);\n startSchedulerTick(fromTime, composition);\n },\n\n pause(): void {\n clearSchedulerTick();\n stopAllSources();\n scheduledClipIds = new Set();\n _isPlaying = false;\n },\n\n seek(time: number, composition: Composition): void {\n clearSchedulerTick();\n stopAllSources();\n scheduledClipIds = new Set();\n _prevTimelineTime = time;\n scheduleComposition(time, composition);\n startSchedulerTick(time, composition);\n },\n\n setPlaybackRate(rate: number): void {\n _playbackRate = rate;\n // If playing, reschedule everything with new rate so future clip\n // contextStartTime values are recalculated correctly.\n if (_isPlaying && currentComposition && _getCurrentTime) {\n stopAllSources();\n scheduledClipIds = new Set();\n const currentTime = _getCurrentTime();\n scheduleComposition(currentTime, currentComposition);\n clearSchedulerTick();\n startSchedulerTick(currentTime, currentComposition);\n } else {\n // Not playing or no getCurrentTime — just update active sources\n for (const scheduled of activeSources) {\n scheduled.source.playbackRate.value = rate;\n }\n }\n },\n\n setTrackVolume(trackId: string, volume: number): void {\n trackVolumes.set(trackId, volume);\n const node = trackGains.get(trackId);\n if (node) {\n // Only apply gain if the track is not muted\n const isMuted = trackMutedStates.get(trackId) ?? false;\n if (!isMuted) {\n node.gain.setValueAtTime(volume, audioContext.currentTime);\n }\n }\n },\n\n setTrackMute(trackId: string, muted: boolean): void {\n trackMutedStates.set(trackId, muted);\n const node = trackGains.get(trackId);\n if (node) {\n if (muted) {\n node.gain.value = 0;\n } else {\n const vol = trackVolumes.get(trackId) ?? 1;\n node.gain.value = vol;\n }\n }\n },\n\n destroy(): void {\n clearSchedulerTick();\n stopAllSources();\n scheduledClipIds = new Set();\n currentComposition = null;\n _isPlaying = false;\n for (const node of trackGains.values()) {\n node.disconnect();\n }\n trackGains.clear();\n trackVolumes.clear();\n trackMutedStates.clear();\n clipBuffers.clear();\n masterGain.disconnect();\n },\n };\n\n return scheduler;\n}\n","import type {\n PlaybackEngine,\n PlaybackState,\n RenderedFrame,\n AssetResolver,\n MediaDecoder,\n Compositor,\n FrameRenderer,\n MasterClock,\n AudioScheduler,\n SubtitleRenderer,\n} from './types.js';\nimport type { Composition } from '@pneuma-craft/timeline';\nimport { createMediaDecoder } from './media-decoder.js';\nimport { createCompositor, type CompositorType } from './compositor.js';\nimport { createFrameRenderer } from './frame-renderer.js';\nimport { createMasterClock } from './master-clock.js';\nimport { createAudioScheduler } from './audio-scheduler.js';\n\nexport interface PlaybackEngineOptions {\n compositorType?: CompositorType;\n /** Max time a single frame decode may block before the loop skips it. Default 1000ms. */\n decodeTimeoutMs?: number;\n /**\n * Optional rasterizer for subtitle-track clips. When provided, active\n * subtitle clips are composited on top of the video layers — the same code\n * path used for export, so preview and export stay in lockstep. When\n * omitted, subtitle tracks render nothing (legacy behavior).\n */\n subtitleRenderer?: SubtitleRenderer;\n /**\n * Render preview frames (planning-layer visuals attached to time points\n * on a track) when no real clip covers the current time. Defaults to\n * `true` for the playback engine — the user scrubbing the timeline\n * should see the agent's planned vibe before any real video clips land.\n */\n includePreviewFrames?: boolean;\n}\n\n/** Default per-frame decode timeout. Decodes exceeding this are logged and skipped. */\nconst DEFAULT_DECODE_TIMEOUT_MS = 1000;\n\n/** Enables verbose rAF-loop logs for debugging playback stalls. */\nconst DEBUG_LOOP = typeof globalThis !== 'undefined'\n && (globalThis as { __PNEUMA_CRAFT_PLAYBACK_DEBUG__?: boolean }).__PNEUMA_CRAFT_PLAYBACK_DEBUG__ === true;\n\nexport function createPlaybackEngine(options?: PlaybackEngineOptions): PlaybackEngine {\n const compositorType = options?.compositorType ?? 'canvas2d';\n const decodeTimeoutMs = options?.decodeTimeoutMs ?? DEFAULT_DECODE_TIMEOUT_MS;\n const subtitleRenderer = options?.subtitleRenderer;\n const includePreviewFrames = options?.includePreviewFrames ?? true;\n\n let _state: PlaybackState = 'idle';\n let _playbackRate = 1;\n let _loop: { start: number; end: number } | null = null;\n\n // Subsystems (created on load)\n let _decoder: MediaDecoder | null = null;\n let _compositor: Compositor | null = null;\n let _frameRenderer: FrameRenderer | null = null;\n let _clock: MasterClock | null = null;\n let _audioScheduler: AudioScheduler | null = null;\n let _audioContext: AudioContext | null = null;\n let _composition: Composition | null = null;\n\n // rAF handle\n let _rafId: number | null = null;\n\n // True while a frame decode is awaiting — prevents stacking concurrent renders\n // when one decode runs slower than a rAF tick.\n let _frameInFlight = false;\n\n // Seek ID counter for discarding stale paused-seek renders\n let _seekId = 0;\n\n // Subscribers\n const stateChangeListeners = new Set<(state: PlaybackState) => void>();\n const timeUpdateListeners = new Set<(time: number) => void>();\n const frameRenderedListeners = new Set<(frame: RenderedFrame) => void>();\n\n function setState(newState: PlaybackState): void {\n if (_state === newState) return;\n _state = newState;\n for (const cb of stateChangeListeners) {\n try { cb(newState); } catch (e) { console.error('[PlaybackEngine] listener error:', e); }\n }\n }\n\n function emitTimeUpdate(time: number): void {\n for (const cb of timeUpdateListeners) {\n try { cb(time); } catch (e) { console.error('[PlaybackEngine] listener error:', e); }\n }\n }\n\n function emitFrameRendered(frame: RenderedFrame): void {\n for (const cb of frameRenderedListeners) {\n try { cb(frame); } catch (e) { console.error('[PlaybackEngine] listener error:', e); }\n }\n }\n\n function destroySubsystems(): void {\n if (_rafId !== null) {\n cancelAnimationFrame(_rafId);\n _rafId = null;\n }\n _frameInFlight = false;\n _frameRenderer?.destroy();\n _clock?.destroy();\n _audioScheduler?.destroy();\n _audioContext?.close();\n _decoder = null;\n _compositor = null;\n _frameRenderer = null;\n _clock = null;\n _audioScheduler = null;\n _audioContext = null;\n _composition = null;\n }\n\n function startRafLoop(): void {\n if (_rafId !== null) return;\n\n // The loop is designed so that time updates and rAF scheduling are INDEPENDENT\n // of frame decoding. This means:\n // 1. If a decode hangs or is slow, the clock/slider still advances and pause/seek\n // remain responsive.\n // 2. At most one decode runs at a time (no concurrent pile-up).\n // 3. A decode exceeding `decodeTimeoutMs` is logged and skipped so the loop can\n // recover from a stuck codec packet pump.\n let lastLoggedState: 'render' | 'skip-inflight' | null = null;\n const logStateChange = (state: 'render' | 'skip-inflight', time: number, extra?: string): void => {\n if (!DEBUG_LOOP) return;\n if (lastLoggedState === state) return;\n lastLoggedState = state;\n console.log(`[engine] ${state} @ t=${time.toFixed(3)}${extra ? ' ' + extra : ''}`);\n };\n\n const loop = (): void => {\n _rafId = null;\n if (_state !== 'playing' || !_clock || !_frameRenderer || !_composition) return;\n\n const time = _clock.currentTime;\n\n // Emit time update up-front — consumers see the clock tick regardless of decode state.\n emitTimeUpdate(time);\n\n // End-of-timeline check.\n if (!_clock.loop && time >= _composition.duration) {\n engine.pause();\n return;\n }\n\n // Always schedule the next rAF synchronously so timing is decoupled from decode.\n _rafId = requestAnimationFrame(loop);\n\n // Skip this tick's render if the previous decode is still in flight — back-pressure\n // so slow decodes don't stack concurrent calls. The watchdog below recovers if a\n // decode never resolves; we never get stuck.\n //\n // NOTE: we intentionally do NOT skip based on _clock.driftMs. A prior implementation\n // did, which caused a deadlock: when the first decode ran long (e.g., cold-start\n // fetch of a 50 MB blob), reportVideoTime captured a large stale-drift value, then\n // every subsequent tick skipped on drift, so reportVideoTime never ran again, and\n // drift was frozen at the stale value forever. The clock is the source of truth;\n // each render just targets its current value, which naturally catches up.\n if (_frameInFlight) {\n logStateChange('skip-inflight', time);\n return;\n }\n\n logStateChange('render', time);\n\n // Kick off the decode. A watchdog timer clears _frameInFlight so the loop can\n // recover if renderFrame never resolves. We attach .then directly (no Promise.race)\n // to preserve the microtask profile — emitFrameRendered fires on the same tick\n // that renderFrame resolves.\n _frameInFlight = true;\n const clockRef = _clock;\n const compositionRef = _composition;\n let timedOut = false;\n const timeoutId = setTimeout(() => {\n timedOut = true;\n _frameInFlight = false;\n console.warn(\n `[PlaybackEngine] decode timeout at t=${time.toFixed(3)}s (>${decodeTimeoutMs}ms) — skipping frame`,\n );\n }, decodeTimeoutMs);\n\n _frameRenderer.renderFrame(compositionRef, time).then(frame => {\n clearTimeout(timeoutId);\n if (timedOut) return;\n _frameInFlight = false;\n if (_state !== 'playing' || _clock !== clockRef) return;\n clockRef.reportVideoTime(time);\n emitFrameRendered(frame);\n }).catch(err => {\n clearTimeout(timeoutId);\n if (timedOut) return;\n _frameInFlight = false;\n console.error('[PlaybackEngine] render error:', err);\n });\n };\n\n _rafId = requestAnimationFrame(loop);\n }\n\n function stopRafLoop(): void {\n if (_rafId !== null) {\n cancelAnimationFrame(_rafId);\n _rafId = null;\n }\n _frameInFlight = false;\n }\n\n const engine: PlaybackEngine = {\n get state() { return _state; },\n\n get currentTime() {\n return _clock?.currentTime ?? 0;\n },\n\n get playbackRate() {\n return _clock?.playbackRate ?? _playbackRate;\n },\n set playbackRate(rate: number) {\n _playbackRate = rate;\n if (_clock) {\n _clock.playbackRate = rate;\n }\n if (_audioScheduler) {\n _audioScheduler.setPlaybackRate(rate);\n }\n },\n\n get loop() {\n return _clock?.loop ?? _loop;\n },\n set loop(l: { start: number; end: number } | null) {\n _loop = l;\n if (_clock) {\n _clock.loop = l;\n }\n },\n\n async load(composition: Composition, resolver: AssetResolver): Promise<void> {\n // Invalidate any pending paused-seek renders from previous session\n _seekId++;\n\n // If previously loaded, destroy old subsystems\n if (_state !== 'idle') {\n destroySubsystems();\n }\n\n setState('loading');\n\n try {\n // Create AudioContext\n _audioContext = new AudioContext();\n\n // Create subsystems\n _decoder = createMediaDecoder(resolver, _audioContext);\n _compositor = await createCompositor(\n composition.settings.width,\n composition.settings.height,\n compositorType,\n );\n _frameRenderer = createFrameRenderer(\n _decoder,\n _compositor,\n composition.settings.width,\n composition.settings.height,\n { subtitleRenderer, includePreviewFrames },\n );\n _clock = createMasterClock({\n audioContext: _audioContext,\n duration: composition.duration,\n frameRate: composition.settings.fps,\n });\n _audioScheduler = createAudioScheduler({ audioContext: _audioContext });\n\n // Apply pending settings\n _clock.playbackRate = _playbackRate;\n _clock.loop = _loop;\n\n _composition = composition;\n\n // Pre-load audio buffers from every clip on every audio OR video track.\n // Video clips carry their own embedded audio (MediaDecoder.decodeAudio\n // extracts it from the container), so they participate in the audio\n // graph just like standalone audio clips. Clips with no audio stream\n // (images, silent videos) throw — we swallow and skip so they simply\n // have no buffer loaded, which makes scheduleClip a no-op for them.\n const mediaClips = composition.tracks\n .filter(track => track.type === 'audio' || track.type === 'video')\n .flatMap(track => track.clips);\n\n for (const clip of mediaClips) {\n try {\n const audioBuffer = await _decoder.decodeAudio(clip.assetId);\n _audioScheduler.loadClip(clip.id, audioBuffer);\n } catch {\n // Clip has no audio (image asset, silent video, or decode failure).\n // Leave the clip without a buffer; audio-scheduler tolerates this.\n }\n }\n\n setState('ready');\n } catch (err) {\n // Clean up any partially-created subsystems\n destroySubsystems();\n setState('idle');\n throw err;\n }\n },\n\n play(): void {\n if (!_clock || !_audioScheduler || !_composition) {\n throw new Error('Cannot play: no composition loaded. Call load() first.');\n }\n if (_state === 'playing') return;\n\n // AudioContext may start in 'suspended' state because it was created\n // inside engine.load() after awaits, losing the user-gesture chain.\n // resume() works as long as ANY user gesture has occurred on the page —\n // clicking the play button is sufficient. Fire-and-forget: audio will\n // unmute as soon as resume settles, which in practice is <1 frame.\n if (_audioContext && _audioContext.state === 'suspended') {\n _audioContext.resume().catch((err) => {\n console.warn('[PlaybackEngine] AudioContext resume failed:', err);\n });\n }\n\n _clock.play();\n _audioScheduler.setPlaybackRate(_playbackRate);\n const clockRef = _clock;\n _audioScheduler.play(_clock.currentTime, _composition, () => clockRef.currentTime);\n setState('playing');\n startRafLoop();\n },\n\n pause(): void {\n if (_state !== 'playing') return;\n\n stopRafLoop();\n _clock?.pause();\n _audioScheduler?.pause();\n setState('paused');\n },\n\n seek(time: number): void {\n if (!_clock || !_audioScheduler || !_composition) {\n throw new Error('Cannot seek: no composition loaded. Call load() first.');\n }\n\n _clock.seek(time);\n // Use the clamped time from clock so audio/video stay in sync\n const clampedTime = _clock.currentTime;\n\n // Only reschedule audio when playing — seeking while paused should not produce sound\n if (_state === 'playing') {\n _audioScheduler.setPlaybackRate(_playbackRate);\n _audioScheduler.seek(clampedTime, _composition);\n }\n\n // If not playing, render the frame at the seeked position\n if (_state !== 'playing' && _frameRenderer) {\n const thisSeekId = ++_seekId;\n _frameRenderer.renderFrame(_composition, clampedTime).then(frame => {\n if (thisSeekId !== _seekId) return; // Stale render, discard\n emitFrameRendered(frame);\n emitTimeUpdate(clampedTime);\n }).catch(err => {\n console.error('[PlaybackEngine] seek render error:', err);\n });\n }\n },\n\n onStateChange(cb: (state: PlaybackState) => void): () => void {\n stateChangeListeners.add(cb);\n return () => { stateChangeListeners.delete(cb); };\n },\n\n onTimeUpdate(cb: (time: number) => void): () => void {\n timeUpdateListeners.add(cb);\n return () => { timeUpdateListeners.delete(cb); };\n },\n\n onFrameRendered(cb: (frame: RenderedFrame) => void): () => void {\n frameRenderedListeners.add(cb);\n return () => { frameRenderedListeners.delete(cb); };\n },\n\n destroy(): void {\n stopRafLoop();\n destroySubsystems();\n stateChangeListeners.clear();\n timeUpdateListeners.clear();\n frameRenderedListeners.clear();\n _playbackRate = 1;\n _loop = null;\n _state = 'idle';\n },\n };\n\n return engine;\n}\n","import type { OfflineAudioRenderer } from './types.js';\nimport type { Clip, Track } from '@pneuma-craft/timeline';\n\nexport function createOfflineAudioRenderer(): OfflineAudioRenderer {\n return {\n async render(composition, _resolver, decodeAudio) {\n const sampleRate = composition.settings.sampleRate ?? 48000;\n const channels = 2;\n const duration = composition.duration;\n const length = Math.ceil(duration * sampleRate);\n\n const offlineCtx = new OfflineAudioContext(channels, length, sampleRate);\n\n // Collect clips from every unmuted media track. Video clips contribute\n // their embedded audio exactly like standalone audio clips — this is\n // what keeps the exported audio in lockstep with what the preview\n // plays. Subtitle/other non-media tracks are skipped.\n const audioClips: Array<{ clip: Clip; track: Track }> = [];\n for (const track of composition.tracks) {\n if (track.type !== 'audio' && track.type !== 'video') continue;\n if (track.muted) continue;\n for (const clip of track.clips) {\n audioClips.push({ clip, track });\n }\n }\n\n // Schedule each clip\n for (const { clip, track } of audioClips) {\n let buffer: AudioBuffer;\n try {\n buffer = await decodeAudio(clip.assetId);\n } catch {\n // Clip has no audio stream (image asset, silent video) or decode\n // failed. Skip silently — video-track clips routinely have no\n // audio, so a warning here would be noise. True decode failures\n // on audio assets surface via the preview path instead.\n continue;\n }\n\n const source = offlineCtx.createBufferSource();\n source.buffer = buffer;\n\n const clipGain = offlineCtx.createGain();\n const clipVolume = clip.volume ?? 1;\n clipGain.gain.value = clipVolume;\n\n const trackGain = offlineCtx.createGain();\n trackGain.gain.value = track.volume;\n\n source.connect(clipGain);\n clipGain.connect(trackGain);\n trackGain.connect(offlineCtx.destination);\n\n // Fade automation\n const fadeIn = clip.fadeIn;\n const fadeOut = clip.fadeOut;\n\n if (fadeIn && fadeIn > 0) {\n clipGain.gain.setValueAtTime(0, clip.startTime);\n clipGain.gain.linearRampToValueAtTime(clipVolume, clip.startTime + fadeIn);\n }\n\n if (fadeOut && fadeOut > 0) {\n const fadeOutStart = clip.startTime + clip.duration - fadeOut;\n clipGain.gain.setValueAtTime(clipVolume, fadeOutStart);\n clipGain.gain.linearRampToValueAtTime(0, clip.startTime + clip.duration);\n }\n\n source.start(clip.startTime, clip.inPoint, clip.duration);\n }\n\n return offlineCtx.startRendering();\n },\n };\n}\n","import type { ExportEngine, ExportOptions, AssetResolver, SubtitleRenderer } from './types.js';\nimport type { Composition } from '@pneuma-craft/timeline';\nimport { createMediaDecoder } from './media-decoder.js';\nimport { createCompositor } from './compositor.js';\nimport { createFrameRenderer } from './frame-renderer.js';\nimport { createOfflineAudioRenderer } from './offline-audio-renderer.js';\nimport {\n Output,\n CanvasSource,\n AudioBufferSource,\n Mp4OutputFormat,\n WebMOutputFormat,\n BufferTarget,\n} from 'mediabunny';\n\nexport interface ExportEngineOptions {\n /**\n * Optional rasterizer for subtitle-track clips. Pass the same renderer used\n * by `createPlaybackEngine` so the exported video is pixel-identical to the\n * preview. When omitted, subtitle tracks are skipped during export.\n */\n subtitleRenderer?: SubtitleRenderer;\n /**\n * Include preview frames (planning-layer visuals attached to time points\n * on a track) in the exported video. Defaults to `false` because an export\n * normally represents a finished cut. Set true for review-grade exports of\n * unfinished timelines — e.g., agent producing a \"草样片\" for the user to\n * scrub before committing to expensive real renders.\n */\n includePreviewFrames?: boolean;\n}\n\nexport function createExportEngine(options?: ExportEngineOptions): ExportEngine {\n const subtitleRenderer = options?.subtitleRenderer;\n const includePreviewFrames = options?.includePreviewFrames ?? false;\n const progressListeners = new Set<(progress: number) => void>();\n let abortController: AbortController | null = null;\n\n function notifyProgress(progress: number): void {\n for (const cb of progressListeners) {\n try { cb(progress); } catch (e) { console.error('[ExportEngine]', e); }\n }\n }\n\n return {\n async export(\n composition: Composition,\n options: ExportOptions,\n resolver: AssetResolver,\n ): Promise<Blob> {\n abortController = new AbortController();\n const signal = abortController.signal;\n\n const fps = options.fps ?? composition.settings.fps;\n const width = options.width ?? composition.settings.width;\n const height = options.height ?? composition.settings.height;\n const totalFrames = Math.ceil(composition.duration * fps);\n\n // Minimal OfflineAudioContext used only for decodeAudioData during asset\n // decoding (export has its own offline context for actual audio rendering).\n const decoderAudioCtx = new OfflineAudioContext(1, 1, composition.settings.sampleRate || 48000);\n const decoder = createMediaDecoder(resolver, decoderAudioCtx);\n const compositor = await createCompositor(width, height, 'canvas2d');\n const renderer = createFrameRenderer(decoder, compositor, width, height, {\n subtitleRenderer,\n includePreviewFrames,\n });\n\n try {\n const format = options.format === 'webm'\n ? new WebMOutputFormat()\n : new Mp4OutputFormat({ fastStart: 'in-memory' });\n\n const target = new BufferTarget();\n const output = new Output({ format, target });\n\n const renderCanvas = new OffscreenCanvas(width, height);\n const videoSource = new CanvasSource(renderCanvas, {\n codec: options.videoCodec,\n bitrate: options.videoBitrate,\n });\n\n const audioSource = new AudioBufferSource({\n codec: options.audioCodec,\n bitrate: options.audioBitrate,\n });\n\n output.addVideoTrack(videoSource);\n output.addAudioTrack(audioSource);\n await output.start();\n\n // Render video frames\n const renderCtx = renderCanvas.getContext('2d')!;\n\n for (let frame = 0; frame < totalFrames; frame++) {\n if (signal.aborted) throw new Error('Export aborted');\n\n const time = frame / fps;\n const rendered = await renderer.renderFrame(composition, time);\n\n renderCtx.clearRect(0, 0, width, height);\n renderCtx.drawImage(rendered.image, 0, 0, width, height);\n rendered.image.close();\n\n await videoSource.add(time, 1 / fps);\n\n if ((frame + 1) % 5 === 0 || frame === totalFrames - 1) {\n notifyProgress((frame + 1) / totalFrames);\n }\n\n // Yield to event loop periodically\n if ((frame + 1) % 5 === 0) {\n await new Promise(r => setTimeout(r, 0));\n }\n }\n\n videoSource.close();\n if (signal.aborted) throw new Error('Export aborted');\n\n // Render audio offline\n const offlineRenderer = createOfflineAudioRenderer();\n const audioBuffer = await offlineRenderer.render(\n composition,\n resolver,\n (assetId) => decoder.decodeAudio(assetId),\n );\n if (signal.aborted) throw new Error('Export aborted');\n\n await audioSource.add(audioBuffer);\n audioSource.close();\n if (signal.aborted) throw new Error('Export aborted');\n\n await output.finalize();\n\n const buffer = target.buffer;\n if (!buffer) throw new Error('Export produced no output');\n\n const mimeType = options.format === 'webm' ? 'video/webm' : 'video/mp4';\n return new Blob([buffer], { type: mimeType });\n } finally {\n renderer.destroy();\n abortController = null;\n }\n },\n\n onProgress(cb: (progress: number) => void): () => void {\n progressListeners.add(cb);\n return () => { progressListeners.delete(cb); };\n },\n\n abort(): void {\n abortController?.abort();\n },\n };\n}\n"],"mappings":"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","names":["import_mediabunny","options"]}
|