@meframe/core 0.0.35 → 0.0.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/defaults.d.ts +1 -1
- package/dist/config/defaults.js +2 -2
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/presets.js +4 -4
- package/dist/config/presets.js.map +1 -1
- package/dist/orchestrator/ExportScheduler.d.ts.map +1 -1
- package/dist/orchestrator/ExportScheduler.js +11 -6
- package/dist/orchestrator/ExportScheduler.js.map +1 -1
- package/dist/orchestrator/OnDemandVideoSession.d.ts.map +1 -1
- package/dist/orchestrator/OnDemandVideoSession.js +23 -1
- package/dist/orchestrator/OnDemandVideoSession.js.map +1 -1
- package/dist/orchestrator/Orchestrator.d.ts +5 -0
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +21 -2
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/stages/decode/BaseDecoder.d.ts.map +1 -1
- package/dist/stages/decode/BaseDecoder.js +35 -1
- package/dist/stages/decode/BaseDecoder.js.map +1 -1
- package/dist/stages/encode/BaseEncoder.d.ts.map +1 -1
- package/dist/stages/encode/BaseEncoder.js +31 -1
- package/dist/stages/encode/BaseEncoder.js.map +1 -1
- package/dist/stages/encode/VideoChunkEncoder.d.ts.map +1 -1
- package/dist/stages/encode/types.d.ts +1 -0
- package/dist/stages/encode/types.d.ts.map +1 -1
- package/dist/workers/{BaseDecoder.CTW-vr29.js → BaseDecoder.DWHTBMDB.js} +36 -2
- package/dist/workers/BaseDecoder.DWHTBMDB.js.map +1 -0
- package/dist/workers/stages/decode/{audio-decode.worker.CP8bXXa4.js → audio-decode.worker._DbEy3nn.js} +2 -2
- package/dist/workers/stages/decode/{audio-decode.worker.CP8bXXa4.js.map → audio-decode.worker._DbEy3nn.js.map} +1 -1
- package/dist/workers/stages/decode/{video-decode.worker.BIspTxgV.js → video-decode.worker.qinOL8Zt.js} +2 -2
- package/dist/workers/stages/decode/{video-decode.worker.BIspTxgV.js.map → video-decode.worker.qinOL8Zt.js.map} +1 -1
- package/dist/workers/stages/encode/{video-encode.worker.u2o7iXCT.js → video-encode.worker.CXgr5E16.js} +37 -4
- package/dist/workers/stages/encode/video-encode.worker.CXgr5E16.js.map +1 -0
- package/dist/workers/worker-manifest.json +3 -3
- package/package.json +1 -1
- package/dist/workers/BaseDecoder.CTW-vr29.js.map +0 -1
- package/dist/workers/stages/encode/video-encode.worker.u2o7iXCT.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ResolvedConfig } from './types';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Default configuration values based on
|
|
4
|
+
* Default configuration values based on 1080p30 memory budget
|
|
5
5
|
*
|
|
6
6
|
* Note: Canvas dimensions are fixed for the entire project.
|
|
7
7
|
* All video clips will be scaled/fitted to this resolution.
|
package/dist/config/defaults.js
CHANGED
|
@@ -4,8 +4,8 @@ const DEFAULT_CONFIG = {
|
|
|
4
4
|
projectId: `project-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
5
5
|
logLevel: "info",
|
|
6
6
|
enablePerfMonitor: false,
|
|
7
|
-
defaultCanvasWidth: CANVAS_PRESETS.
|
|
8
|
-
defaultCanvasHeight: CANVAS_PRESETS.
|
|
7
|
+
defaultCanvasWidth: CANVAS_PRESETS.HD_PORTRAIT.width,
|
|
8
|
+
defaultCanvasHeight: CANVAS_PRESETS.HD_PORTRAIT.height,
|
|
9
9
|
defaultFps: 30,
|
|
10
10
|
workerPath: "/meframe-workers",
|
|
11
11
|
workerExtension: ".js"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.js","sources":["../../src/config/defaults.ts"],"sourcesContent":["import type { ResolvedConfig } from './types';\nimport { CANVAS_PRESETS } from './presets';\n\n/**\n * Detect if running in development mode\n * In dev mode, workers are loaded from source (Vite dev server)\n * In prod mode, workers are loaded from dist\n */\nconst isDev = import.meta.env?.DEV ?? false;\n\n/**\n * Default configuration values based on
|
|
1
|
+
{"version":3,"file":"defaults.js","sources":["../../src/config/defaults.ts"],"sourcesContent":["import type { ResolvedConfig } from './types';\nimport { CANVAS_PRESETS } from './presets';\n\n/**\n * Detect if running in development mode\n * In dev mode, workers are loaded from source (Vite dev server)\n * In prod mode, workers are loaded from dist\n */\nconst isDev = import.meta.env?.DEV ?? false;\n\n/**\n * Default configuration values based on 1080p30 memory budget\n *\n * Note: Canvas dimensions are fixed for the entire project.\n * All video clips will be scaled/fitted to this resolution.\n * Choose based on your primary content type:\n * - 720×1280: Mobile vertical video\n * - 1080×1920: HD vertical video (TikTok, Instagram Reels) ← default\n * - 1920×1080: HD horizontal video (YouTube)\n */\nexport const DEFAULT_CONFIG: ResolvedConfig = {\n global: {\n projectId: `project-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,\n logLevel: 'info',\n enablePerfMonitor: false,\n defaultCanvasWidth: CANVAS_PRESETS.HD_PORTRAIT.width,\n defaultCanvasHeight: CANVAS_PRESETS.HD_PORTRAIT.height,\n defaultFps: 30,\n workerPath: isDev ? '/src' : '/meframe-workers',\n workerExtension: isDev ? '.ts' : '.js',\n },\n\n load: {\n maxConcurrent: 2,\n backpressure: {\n highWaterMark: 64 * 1024, // 64 KB\n stallTimeoutMs: 500,\n },\n retry: {\n maxAttempts: 3,\n baseDelayMs: 500,\n },\n window: {\n maxInflightPerClip: 1,\n maxInflight: 4,\n chunkSize: 1 * 1024 * 1024, // 1 MB\n },\n },\n\n demux: {\n backpressure: {\n highWaterMark: 10, // 10 EncodedChunks\n },\n },\n\n decode: {\n video: {\n backpressure: {\n highWaterMark: 4, // 4 EncodedVideoChunks\n decodeQueueThreshold: 16, // Pause when decoder has 16+ chunks\n },\n maxGOPs: 4, // Cache 4 GOPs for seeking\n },\n audio: {\n backpressure: {\n highWaterMark: 20, // 20 EncodedAudioChunks\n },\n },\n },\n\n compose: {\n visual: {},\n audio: {\n enableDucking: false, // Default: no ducking\n },\n },\n\n encode: {\n video: {},\n audio: {},\n },\n\n cache: {\n l1: {\n windowSizeUs: 6_000_000, // ±3 seconds window\n maxMemoryMB: 200,\n },\n l2: {\n autoFill: true,\n fillPriority: 'low' as const,\n },\n opfs: {\n enabled: true,\n resource: {\n maxSizeMB: 5120, // 5GB for original resources\n evictionPolicy: 'lru' as const,\n },\n l2: {\n maxSizeMB: 2048, // 2GB for encoded chunks\n },\n },\n },\n\n mux: {},\n};\n\n/**\n * Tuning presets for common scenarios\n */\nexport const TUNING_PRESETS = {\n /**\n * Low-latency live streaming\n * Minimize buffering for real-time playback\n */\n lowLatency: {\n global: {\n logLevel: 'info' as const,\n },\n load: {\n backpressure: {\n highWaterMark: 16 * 1024, // 16 KB\n },\n },\n demux: {\n backpressure: {\n highWaterMark: 3,\n },\n },\n decode: {\n video: {\n backpressure: {\n highWaterMark: 2,\n decodeQueueThreshold: 4,\n },\n },\n audio: {\n backpressure: {\n highWaterMark: 10,\n },\n },\n },\n },\n\n /**\n * 4K60 playback/export\n * Larger buffers for high bitrate content\n */\n highQuality: {\n global: {\n logLevel: 'info' as const,\n },\n load: {\n backpressure: {\n highWaterMark: 256 * 1024, // 256 KB\n },\n retry: {\n maxAttempts: 3,\n baseDelayMs: 500,\n },\n },\n demux: {\n backpressure: {\n highWaterMark: 20,\n },\n },\n decode: {\n video: {\n backpressure: {\n highWaterMark: 8,\n decodeQueueThreshold: 24,\n },\n codecHints: ['h264', 'hevc'] as ('h264' | 'hevc')[],\n },\n audio: {\n backpressure: {\n highWaterMark: 30,\n },\n },\n },\n cache: {\n l2: {\n quotaGb: 1,\n },\n },\n },\n\n /**\n * Batch offline transcode\n * Maximum throughput, memory not a concern\n */\n offline: {\n global: {\n logLevel: 'warn' as const,\n },\n load: {\n backpressure: {\n highWaterMark: 512 * 1024, // 512 KB\n },\n },\n demux: {\n backpressure: {\n highWaterMark: 25,\n },\n },\n decode: {\n video: {\n backpressure: {\n highWaterMark: 12,\n decodeQueueThreshold: 32,\n },\n },\n audio: {\n backpressure: {\n highWaterMark: 40,\n },\n },\n },\n },\n} as const;\n"],"names":[],"mappings":"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"}
|
package/dist/config/presets.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presets.js","sources":["../../src/config/presets.ts"],"sourcesContent":["/**\n * Predefined canvas size presets for common use cases\n */\n\nexport const CANVAS_PRESETS = {\n // Mobile vertical (default)\n MOBILE_PORTRAIT: {\n width: 720,\n height: 1280,\n description: 'Mobile vertical video (720p)',\n },\n\n // HD vertical (social media)\n HD_PORTRAIT: {\n width: 1080,\n height: 1920,\n description: 'HD vertical video (TikTok, Instagram Reels)',\n },\n\n // HD horizontal (YouTube)\n HD_LANDSCAPE: {\n width: 1920,\n height: 1080,\n description: 'HD horizontal video (YouTube, standard HD)',\n },\n\n // 4K horizontal\n UHD_LANDSCAPE: {\n width: 3840,\n height: 2160,\n description: '4K horizontal video (Ultra HD)',\n },\n\n // Square (Instagram post)\n SQUARE_HD: {\n width: 1080,\n height: 1080,\n description: 'Square HD (Instagram post)',\n },\n} as const;\n\nexport type CanvasPreset = keyof typeof CANVAS_PRESETS;\n"],"names":[],"mappings":"AAIO,MAAM,iBAAiB;AAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"presets.js","sources":["../../src/config/presets.ts"],"sourcesContent":["/**\n * Predefined canvas size presets for common use cases\n */\n\nexport const CANVAS_PRESETS = {\n // Mobile vertical (default)\n MOBILE_PORTRAIT: {\n width: 720,\n height: 1280,\n description: 'Mobile vertical video (720p)',\n },\n\n // HD vertical (social media)\n HD_PORTRAIT: {\n width: 1080,\n height: 1920,\n description: 'HD vertical video (TikTok, Instagram Reels)',\n },\n\n // HD horizontal (YouTube)\n HD_LANDSCAPE: {\n width: 1920,\n height: 1080,\n description: 'HD horizontal video (YouTube, standard HD)',\n },\n\n // 4K horizontal\n UHD_LANDSCAPE: {\n width: 3840,\n height: 2160,\n description: '4K horizontal video (Ultra HD)',\n },\n\n // Square (Instagram post)\n SQUARE_HD: {\n width: 1080,\n height: 1080,\n description: 'Square HD (Instagram post)',\n },\n} as const;\n\nexport type CanvasPreset = keyof typeof CANVAS_PRESETS;\n"],"names":[],"mappings":"AAIO,MAAM,iBAAiB;AAAA;AAAA,EAS5B,aAAa;AAAA,IACX,OAAO;AAAA,IACP,QAAQ;AAAA,EAEV;AAsBF;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExportScheduler.d.ts","sourceRoot":"","sources":["../../src/orchestrator/ExportScheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAgB,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEhE,UAAU,mBAAmB;IAC3B,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,EAAE,kBAAkB,CAAC;IAC5B,YAAY,EAAE,YAAY,CAAC;IAC3B,cAAc,EAAE,cAAc,CAAC;IAC/B,UAAU,EAAE,UAAU,CAAC;IACvB,YAAY,EAAE,kBAAkB,CAAC;IACjC,qBAAqB,EAAE,MAAM,MAAM,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IACrD,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;CACrC;AAED,UAAU,qBAAsB,SAAQ,aAAa;IACnD,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,IAAI,CAAsB;gBAEtB,IAAI,EAAE,mBAAmB;IAI/B,OAAO,CAAC,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAuFrF;;OAEG;YACW,gBAAgB;IAwC9B;;;;OAIG;YACW,qBAAqB;YAsBrB,6BAA6B;
|
|
1
|
+
{"version":3,"file":"ExportScheduler.d.ts","sourceRoot":"","sources":["../../src/orchestrator/ExportScheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAgB,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEhE,UAAU,mBAAmB;IAC3B,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,EAAE,kBAAkB,CAAC;IAC5B,YAAY,EAAE,YAAY,CAAC;IAC3B,cAAc,EAAE,cAAc,CAAC;IAC/B,UAAU,EAAE,UAAU,CAAC;IACvB,YAAY,EAAE,kBAAkB,CAAC;IACjC,qBAAqB,EAAE,MAAM,MAAM,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IACrD,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;CACrC;AAED,UAAU,qBAAsB,SAAQ,aAAa;IACnD,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,IAAI,CAAsB;gBAEtB,IAAI,EAAE,mBAAmB;IAI/B,OAAO,CAAC,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAuFrF;;OAEG;YACW,gBAAgB;IAwC9B;;;;OAIG;YACW,qBAAqB;YAsBrB,6BAA6B;CAmI5C"}
|
|
@@ -120,9 +120,11 @@ class ExportScheduler {
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
async processVideoClipsSequentially(clips, muxManager, model, checkStatus) {
|
|
123
|
-
let
|
|
123
|
+
let nextClipStartUs = 0;
|
|
124
|
+
let lastChunkEndUs = 0;
|
|
124
125
|
for (let i = 0; i < clips.length; i++) {
|
|
125
126
|
const clip = clips[i];
|
|
127
|
+
const currentClipOffsetUs = nextClipStartUs;
|
|
126
128
|
await checkStatus();
|
|
127
129
|
const sessionId = `${clip.id}-export`;
|
|
128
130
|
let streamFinishedResolver;
|
|
@@ -154,21 +156,24 @@ class ExportScheduler {
|
|
|
154
156
|
if (value) {
|
|
155
157
|
const originalChunk = value.chunk;
|
|
156
158
|
const metadata = value.metadata;
|
|
159
|
+
const chunkDuration = originalChunk.duration ?? 33333;
|
|
160
|
+
const remappedTimestamp = originalChunk.timestamp + currentClipOffsetUs;
|
|
157
161
|
const buffer = new ArrayBuffer(originalChunk.byteLength);
|
|
158
162
|
originalChunk.copyTo(buffer);
|
|
159
163
|
const remappedChunk = new EncodedVideoChunk({
|
|
160
164
|
type: originalChunk.type,
|
|
161
|
-
timestamp:
|
|
162
|
-
duration:
|
|
165
|
+
timestamp: remappedTimestamp,
|
|
166
|
+
duration: chunkDuration,
|
|
163
167
|
data: buffer
|
|
164
168
|
});
|
|
165
169
|
muxManager.writeVideoChunk(remappedChunk, metadata);
|
|
166
|
-
|
|
170
|
+
lastChunkEndUs = remappedTimestamp + chunkDuration;
|
|
171
|
+
const encodingProgress = remappedTimestamp / model.durationUs;
|
|
167
172
|
const totalProgress = 0.4 + encodingProgress * 0.6;
|
|
168
173
|
this.deps.eventBus.emit(MeframeEvent.ExportProgress, {
|
|
169
174
|
progress: Math.min(1, totalProgress),
|
|
170
175
|
stage: "encoding",
|
|
171
|
-
timeUs:
|
|
176
|
+
timeUs: remappedTimestamp
|
|
172
177
|
});
|
|
173
178
|
}
|
|
174
179
|
}
|
|
@@ -214,7 +219,7 @@ class ExportScheduler {
|
|
|
214
219
|
} finally {
|
|
215
220
|
await session.dispose();
|
|
216
221
|
}
|
|
217
|
-
|
|
222
|
+
nextClipStartUs = lastChunkEndUs;
|
|
218
223
|
}
|
|
219
224
|
}
|
|
220
225
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExportScheduler.js","sources":["../../src/orchestrator/ExportScheduler.ts"],"sourcesContent":["import { CompositionModel } from '../model';\nimport { ExportOptions } from '../types';\nimport { WorkerPool } from '../worker/WorkerPool';\nimport { CompositionPlanner } from './CompositionPlanner';\nimport { CacheManager } from '../cache/CacheManager';\nimport { ResourceLoader } from '../stages/load/ResourceLoader';\nimport { MuxManager } from '../stages/mux/MuxManager';\nimport { GlobalAudioSession } from './GlobalAudioSession';\nimport { VideoClipSession } from './VideoClipSession';\nimport { WorkerType } from '../worker/types';\nimport { hasResourceId, type TimeUs } from '../model/types';\nimport type { ExportController } from '../controllers/ExportController';\nimport { EventBus } from '../event/EventBus';\nimport { MeframeEvent, EventPayloadMap } from '../event/events';\n\ninterface ExportSchedulerDeps {\n workerPool: WorkerPool;\n planner: CompositionPlanner;\n cacheManager: CacheManager;\n resourceLoader: ResourceLoader;\n muxManager: MuxManager;\n audioSession: GlobalAudioSession;\n workerConfigsProvider: () => Record<WorkerType, any>;\n eventBus: EventBus<EventPayloadMap>;\n}\n\ninterface ExtendedExportOptions extends ExportOptions {\n signal?: AbortSignal;\n controller?: ExportController;\n}\n\nexport class ExportScheduler {\n private deps: ExportSchedulerDeps;\n\n constructor(deps: ExportSchedulerDeps) {\n this.deps = deps;\n }\n\n async execute(model: CompositionModel, options: ExtendedExportOptions): Promise<Blob> {\n const { muxManager, audioSession, eventBus, resourceLoader } = this.deps;\n const signal = options.signal;\n const controller = options.controller;\n\n const checkStatus = async () => {\n if (signal?.aborted) {\n throw new DOMException('Export aborted', 'AbortError');\n }\n if (controller?.isPaused()) {\n // Wait until resumed\n while (controller.isPaused()) {\n if (signal?.aborted) throw new DOMException('Export aborted', 'AbortError');\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n }\n };\n\n const width = options.width || model.renderConfig?.width || 720;\n const height = options.height || model.renderConfig?.height || 1280;\n const fps = options.fps || model.fps || 30;\n\n eventBus.emit(MeframeEvent.ExportStart, {\n format: options.format || 'mp4',\n width,\n height,\n fps,\n durationUs: model.durationUs,\n });\n\n try {\n // 1. Preload and parse all resources (0-40%)\n await this.preloadResources(model, resourceLoader, eventBus, checkStatus);\n\n // 2. Start Muxer\n muxManager.start({\n width,\n height,\n fps,\n });\n\n // 3. Process Video and Audio\n const mainTrack = model.tracks.find((t) => t.id === model.mainTrackId);\n if (mainTrack && mainTrack.clips.length > 0) {\n // Process audio with 60s windows (balance quality and memory)\n const audioPromise = this.processAudioInWindows(\n model.durationUs,\n audioSession,\n muxManager,\n checkStatus\n );\n\n // Process video clips sequentially\n await this.processVideoClipsSequentially(mainTrack.clips, muxManager, model, checkStatus);\n\n // Wait for audio encoding to complete\n await audioPromise;\n } else {\n console.warn('[ExportScheduler] No video clips found');\n }\n\n // Finalize audio session (close encoder)\n await audioSession.finalizeExportAudio();\n\n if (signal?.aborted) {\n throw new DOMException('Export aborted', 'AbortError');\n }\n\n // 4. Finalize\n const blob = await muxManager.finalize();\n\n eventBus.emit(MeframeEvent.ExportComplete, {\n size: blob.size,\n durationMs: model.durationUs / 1000,\n format: options.format || 'mp4',\n });\n\n return blob;\n } catch (error) {\n eventBus.emit(MeframeEvent.ExportError, {\n error: error instanceof Error ? error : new Error(String(error)),\n stage: 'export',\n });\n throw error;\n }\n }\n\n /**\n * Preload all resources (0-40% progress)\n */\n private async preloadResources(\n model: CompositionModel,\n resourceLoader: ResourceLoader,\n eventBus: EventBus<EventPayloadMap>,\n checkStatus: () => Promise<void>\n ): Promise<void> {\n eventBus.emit(MeframeEvent.ExportProgress, {\n progress: 0,\n stage: 'preparing',\n message: 'Loading and parsing resources...',\n });\n\n // Collect all resources to load\n const resourcesToLoad: string[] = [];\n for (const track of model.tracks) {\n if (!['video', 'audio'].includes(track.kind)) continue;\n for (const clip of track.clips) {\n if (hasResourceId(clip)) {\n resourcesToLoad.push(clip.resourceId);\n }\n }\n }\n\n // Load resources with progress updates\n for (let i = 0; i < resourcesToLoad.length; i++) {\n await checkStatus();\n\n const resourceId = resourcesToLoad[i];\n await resourceLoader.load(resourceId, { priority: 'high' });\n\n // Update progress: 0-40%\n const progress = ((i + 1) / resourcesToLoad.length) * 0.4;\n eventBus.emit(MeframeEvent.ExportProgress, {\n progress,\n stage: 'preparing',\n message: `Loading resources... (${i + 1}/${resourcesToLoad.length})`,\n });\n }\n }\n\n /**\n * Process audio in 60-second windows\n * - Videos ≤60s: Single pass (zero boundaries)\n * - Videos >60s: 60s windows (minimal boundaries, ~23MB per window)\n */\n private async processAudioInWindows(\n totalDurationUs: TimeUs,\n audioSession: GlobalAudioSession,\n muxManager: MuxManager,\n checkStatus: () => Promise<void>\n ): Promise<void> {\n const WINDOW_DURATION_US = 5 * 60 * 1_000_000; // 5 minutes\n let currentUs = 0;\n\n while (currentUs < totalDurationUs) {\n await checkStatus();\n\n const endUs = Math.min(currentUs + WINDOW_DURATION_US, totalDurationUs);\n\n await audioSession.mixAndEncodeSegment(currentUs, endUs, (chunk, meta) =>\n muxManager.writeAudioChunk(chunk, meta)\n );\n\n currentUs = endUs;\n }\n }\n\n private async processVideoClipsSequentially(\n clips: any[],\n muxManager: MuxManager,\n model: CompositionModel,\n checkStatus: () => Promise<void>\n ) {\n let accumulatedDurationUs = 0;\n\n for (let i = 0; i < clips.length; i++) {\n const clip = clips[i];\n // const nextClip = clips[i + 1];\n\n await checkStatus(); // Check before starting new clip\n\n const sessionId = `${clip.id}-export`;\n let streamFinishedResolver: () => void;\n let streamFinishedRejecter: (err: any) => void;\n const streamFinishedPromise = new Promise<void>((resolve, reject) => {\n streamFinishedResolver = resolve;\n streamFinishedRejecter = reject;\n });\n\n const session = await VideoClipSession.create({\n clipId: clip.id,\n sessionId,\n forL2Only: true,\n planner: this.deps.planner,\n workerPool: this.deps.workerPool,\n cacheManager: this.deps.cacheManager,\n compositionModel: model,\n workerConfigs: this.deps.workerConfigsProvider(),\n callbacks: {\n onComposedStreamReady: async () => {},\n onEncodedStreamReady: async (stream, track) => {\n if (track === 'video') {\n const reader = stream.getReader();\n try {\n while (true) {\n await checkStatus();\n\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n const originalChunk = value.chunk;\n const metadata = value.metadata;\n\n const buffer = new ArrayBuffer(originalChunk.byteLength);\n originalChunk.copyTo(buffer);\n\n const remappedChunk = new EncodedVideoChunk({\n type: originalChunk.type,\n timestamp: originalChunk.timestamp + accumulatedDurationUs,\n duration: originalChunk.duration ?? undefined,\n data: buffer,\n });\n\n muxManager.writeVideoChunk(remappedChunk, metadata);\n\n // Emit progress: 40-100%\n const encodingProgress =\n (accumulatedDurationUs + originalChunk.timestamp) / model.durationUs;\n const totalProgress = 0.4 + encodingProgress * 0.6; // 40% + (0-60%)\n\n this.deps.eventBus.emit(MeframeEvent.ExportProgress, {\n progress: Math.min(1.0, totalProgress),\n stage: 'encoding',\n timeUs: accumulatedDurationUs + originalChunk.timestamp,\n });\n }\n }\n streamFinishedResolver();\n } catch (error) {\n if (error instanceof DOMException && error.name === 'AbortError') {\n streamFinishedRejecter(error);\n } else {\n console.error(`[ExportScheduler] Stream error for clip ${clip.id}:`, error);\n streamFinishedRejecter(error);\n }\n } finally {\n reader.releaseLock();\n }\n }\n },\n onPipelineReady: async (attachmentResourceIds) => {\n // Load Main Track Resource\n if (hasResourceId(clip)) {\n await this.deps.resourceLoader.load(clip.resourceId, {\n priority: 'high',\n sessionId,\n clipId: clip.id,\n trackId: clip.trackId,\n isMainTrack: true,\n });\n }\n\n // Load Attachment Resources\n if (attachmentResourceIds) {\n for (const rid of attachmentResourceIds) {\n await this.deps.resourceLoader.load(rid, {\n priority: 'normal',\n sessionId,\n clipId: clip.id,\n isMainTrack: false,\n });\n }\n }\n },\n },\n });\n\n try {\n await session.activate();\n await streamFinishedPromise;\n } finally {\n await session.dispose();\n }\n\n accumulatedDurationUs += clip.durationUs;\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ExportScheduler.js","sources":["../../src/orchestrator/ExportScheduler.ts"],"sourcesContent":["import { CompositionModel } from '../model';\nimport { ExportOptions } from '../types';\nimport { WorkerPool } from '../worker/WorkerPool';\nimport { CompositionPlanner } from './CompositionPlanner';\nimport { CacheManager } from '../cache/CacheManager';\nimport { ResourceLoader } from '../stages/load/ResourceLoader';\nimport { MuxManager } from '../stages/mux/MuxManager';\nimport { GlobalAudioSession } from './GlobalAudioSession';\nimport { VideoClipSession } from './VideoClipSession';\nimport { WorkerType } from '../worker/types';\nimport { hasResourceId, type TimeUs } from '../model/types';\nimport type { ExportController } from '../controllers/ExportController';\nimport { EventBus } from '../event/EventBus';\nimport { MeframeEvent, EventPayloadMap } from '../event/events';\n\ninterface ExportSchedulerDeps {\n workerPool: WorkerPool;\n planner: CompositionPlanner;\n cacheManager: CacheManager;\n resourceLoader: ResourceLoader;\n muxManager: MuxManager;\n audioSession: GlobalAudioSession;\n workerConfigsProvider: () => Record<WorkerType, any>;\n eventBus: EventBus<EventPayloadMap>;\n}\n\ninterface ExtendedExportOptions extends ExportOptions {\n signal?: AbortSignal;\n controller?: ExportController;\n}\n\nexport class ExportScheduler {\n private deps: ExportSchedulerDeps;\n\n constructor(deps: ExportSchedulerDeps) {\n this.deps = deps;\n }\n\n async execute(model: CompositionModel, options: ExtendedExportOptions): Promise<Blob> {\n const { muxManager, audioSession, eventBus, resourceLoader } = this.deps;\n const signal = options.signal;\n const controller = options.controller;\n\n const checkStatus = async () => {\n if (signal?.aborted) {\n throw new DOMException('Export aborted', 'AbortError');\n }\n if (controller?.isPaused()) {\n // Wait until resumed\n while (controller.isPaused()) {\n if (signal?.aborted) throw new DOMException('Export aborted', 'AbortError');\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n }\n };\n\n const width = options.width || model.renderConfig?.width || 720;\n const height = options.height || model.renderConfig?.height || 1280;\n const fps = options.fps || model.fps || 30;\n\n eventBus.emit(MeframeEvent.ExportStart, {\n format: options.format || 'mp4',\n width,\n height,\n fps,\n durationUs: model.durationUs,\n });\n\n try {\n // 1. Preload and parse all resources (0-40%)\n await this.preloadResources(model, resourceLoader, eventBus, checkStatus);\n\n // 2. Start Muxer\n muxManager.start({\n width,\n height,\n fps,\n });\n\n // 3. Process Video and Audio\n const mainTrack = model.tracks.find((t) => t.id === model.mainTrackId);\n if (mainTrack && mainTrack.clips.length > 0) {\n // Process audio with 60s windows (balance quality and memory)\n const audioPromise = this.processAudioInWindows(\n model.durationUs,\n audioSession,\n muxManager,\n checkStatus\n );\n\n // Process video clips sequentially\n await this.processVideoClipsSequentially(mainTrack.clips, muxManager, model, checkStatus);\n\n // Wait for audio encoding to complete\n await audioPromise;\n } else {\n console.warn('[ExportScheduler] No video clips found');\n }\n\n // Finalize audio session (close encoder)\n await audioSession.finalizeExportAudio();\n\n if (signal?.aborted) {\n throw new DOMException('Export aborted', 'AbortError');\n }\n\n // 4. Finalize\n const blob = await muxManager.finalize();\n\n eventBus.emit(MeframeEvent.ExportComplete, {\n size: blob.size,\n durationMs: model.durationUs / 1000,\n format: options.format || 'mp4',\n });\n\n return blob;\n } catch (error) {\n eventBus.emit(MeframeEvent.ExportError, {\n error: error instanceof Error ? error : new Error(String(error)),\n stage: 'export',\n });\n throw error;\n }\n }\n\n /**\n * Preload all resources (0-40% progress)\n */\n private async preloadResources(\n model: CompositionModel,\n resourceLoader: ResourceLoader,\n eventBus: EventBus<EventPayloadMap>,\n checkStatus: () => Promise<void>\n ): Promise<void> {\n eventBus.emit(MeframeEvent.ExportProgress, {\n progress: 0,\n stage: 'preparing',\n message: 'Loading and parsing resources...',\n });\n\n // Collect all resources to load\n const resourcesToLoad: string[] = [];\n for (const track of model.tracks) {\n if (!['video', 'audio'].includes(track.kind)) continue;\n for (const clip of track.clips) {\n if (hasResourceId(clip)) {\n resourcesToLoad.push(clip.resourceId);\n }\n }\n }\n\n // Load resources with progress updates\n for (let i = 0; i < resourcesToLoad.length; i++) {\n await checkStatus();\n\n const resourceId = resourcesToLoad[i];\n await resourceLoader.load(resourceId, { priority: 'high' });\n\n // Update progress: 0-40%\n const progress = ((i + 1) / resourcesToLoad.length) * 0.4;\n eventBus.emit(MeframeEvent.ExportProgress, {\n progress,\n stage: 'preparing',\n message: `Loading resources... (${i + 1}/${resourcesToLoad.length})`,\n });\n }\n }\n\n /**\n * Process audio in 60-second windows\n * - Videos ≤60s: Single pass (zero boundaries)\n * - Videos >60s: 60s windows (minimal boundaries, ~23MB per window)\n */\n private async processAudioInWindows(\n totalDurationUs: TimeUs,\n audioSession: GlobalAudioSession,\n muxManager: MuxManager,\n checkStatus: () => Promise<void>\n ): Promise<void> {\n const WINDOW_DURATION_US = 5 * 60 * 1_000_000; // 5 minutes\n let currentUs = 0;\n\n while (currentUs < totalDurationUs) {\n await checkStatus();\n\n const endUs = Math.min(currentUs + WINDOW_DURATION_US, totalDurationUs);\n\n await audioSession.mixAndEncodeSegment(currentUs, endUs, (chunk, meta) =>\n muxManager.writeAudioChunk(chunk, meta)\n );\n\n currentUs = endUs;\n }\n }\n\n private async processVideoClipsSequentially(\n clips: any[],\n muxManager: MuxManager,\n model: CompositionModel,\n checkStatus: () => Promise<void>\n ) {\n // Use actual last written timestamp + duration as offset for next clip\n // This avoids duplicate PTS when mp4-muxer rounds microseconds to timescale\n let nextClipStartUs = 0;\n // Track last chunk's end time (timestamp + duration) for precise offset calculation\n let lastChunkEndUs = 0;\n\n for (let i = 0; i < clips.length; i++) {\n const clip = clips[i];\n const currentClipOffsetUs = nextClipStartUs;\n\n await checkStatus(); // Check before starting new clip\n\n const sessionId = `${clip.id}-export`;\n let streamFinishedResolver: () => void;\n let streamFinishedRejecter: (err: any) => void;\n const streamFinishedPromise = new Promise<void>((resolve, reject) => {\n streamFinishedResolver = resolve;\n streamFinishedRejecter = reject;\n });\n\n const session = await VideoClipSession.create({\n clipId: clip.id,\n sessionId,\n forL2Only: true,\n planner: this.deps.planner,\n workerPool: this.deps.workerPool,\n cacheManager: this.deps.cacheManager,\n compositionModel: model,\n workerConfigs: this.deps.workerConfigsProvider(),\n callbacks: {\n onComposedStreamReady: async () => {},\n onEncodedStreamReady: async (stream, track) => {\n if (track === 'video') {\n const reader = stream.getReader();\n try {\n while (true) {\n await checkStatus();\n\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n const originalChunk = value.chunk;\n const metadata = value.metadata;\n const chunkDuration = originalChunk.duration ?? 33333; // Default ~30fps\n\n const remappedTimestamp = originalChunk.timestamp + currentClipOffsetUs;\n\n const buffer = new ArrayBuffer(originalChunk.byteLength);\n originalChunk.copyTo(buffer);\n\n const remappedChunk = new EncodedVideoChunk({\n type: originalChunk.type,\n timestamp: remappedTimestamp,\n duration: chunkDuration,\n data: buffer,\n });\n\n muxManager.writeVideoChunk(remappedChunk, metadata);\n\n // Track end time for next clip's offset\n lastChunkEndUs = remappedTimestamp + chunkDuration;\n\n // Emit progress: 40-100%\n const encodingProgress = remappedTimestamp / model.durationUs;\n const totalProgress = 0.4 + encodingProgress * 0.6; // 40% + (0-60%)\n\n this.deps.eventBus.emit(MeframeEvent.ExportProgress, {\n progress: Math.min(1.0, totalProgress),\n stage: 'encoding',\n timeUs: remappedTimestamp,\n });\n }\n }\n streamFinishedResolver();\n } catch (error) {\n if (error instanceof DOMException && error.name === 'AbortError') {\n streamFinishedRejecter(error);\n } else {\n console.error(`[ExportScheduler] Stream error for clip ${clip.id}:`, error);\n streamFinishedRejecter(error);\n }\n } finally {\n reader.releaseLock();\n }\n }\n },\n onPipelineReady: async (attachmentResourceIds) => {\n // Load Main Track Resource\n if (hasResourceId(clip)) {\n await this.deps.resourceLoader.load(clip.resourceId, {\n priority: 'high',\n sessionId,\n clipId: clip.id,\n trackId: clip.trackId,\n isMainTrack: true,\n });\n }\n\n // Load Attachment Resources\n if (attachmentResourceIds) {\n for (const rid of attachmentResourceIds) {\n await this.deps.resourceLoader.load(rid, {\n priority: 'normal',\n sessionId,\n clipId: clip.id,\n isMainTrack: false,\n });\n }\n }\n },\n },\n });\n\n try {\n await session.activate();\n await streamFinishedPromise;\n } finally {\n await session.dispose();\n }\n\n // Use actual last chunk end time as next clip's start\n // This ensures no timestamp overlap even after muxer rounding\n nextClipStartUs = lastChunkEndUs;\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OnDemandVideoSession.d.ts","sourceRoot":"","sources":["../../src/orchestrator/OnDemandVideoSession.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,KAAK,EAAE,QAAQ,EAAO,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAGvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAQpE,UAAU,0BAA0B;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,aAAa,CAAC;IAC7B,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,cAAc,EAAE,cAAc,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,oBAAoB;IAC/B;;;OAGG;WACU,wBAAwB,CACnC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,iBAAiB,EAAE,EAC3B,KAAK,EAAE,QAAQ,EACf,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,YAAY,EAC1B,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC;IAuEhB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;IACpD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IAEtC,OAAO,CAAC,OAAO,CAAkC;IACjD,UAAU,UAAS;IACnB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,aAAa,CAAoB;IAEzC,OAAO;WAYM,MAAM,CAAC,MAAM,EAAE,0BAA0B,GAAG,OAAO,CAAC,oBAAoB,CAAC;YAMxE,IAAI;IAIlB;;OAEG;IACG,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAsEjE,OAAO,CAAC,2BAA2B;IAgDnC;;;;;;OAMG;YACW,YAAY;YA0DZ,YAAY;
|
|
1
|
+
{"version":3,"file":"OnDemandVideoSession.d.ts","sourceRoot":"","sources":["../../src/orchestrator/OnDemandVideoSession.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,KAAK,EAAE,QAAQ,EAAO,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAGvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAQpE,UAAU,0BAA0B;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,aAAa,CAAC;IAC7B,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,cAAc,EAAE,cAAc,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,oBAAoB;IAC/B;;;OAGG;WACU,wBAAwB,CACnC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,iBAAiB,EAAE,EAC3B,KAAK,EAAE,QAAQ,EACf,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,YAAY,EAC1B,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC;IAuEhB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;IACpD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IAEtC,OAAO,CAAC,OAAO,CAAkC;IACjD,UAAU,UAAS;IACnB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,aAAa,CAAoB;IAEzC,OAAO;WAYM,MAAM,CAAC,MAAM,EAAE,0BAA0B,GAAG,OAAO,CAAC,oBAAoB,CAAC;YAMxE,IAAI;IAIlB;;OAEG;IACG,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAsEjE,OAAO,CAAC,2BAA2B;IAgDnC;;;;;;OAMG;YACW,YAAY;YA0DZ,YAAY;YA0EZ,kBAAkB;IA4ChC;;;OAGG;IACG,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAsD5D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CA6B/B"}
|
|
@@ -225,6 +225,13 @@ class OnDemandVideoSession {
|
|
|
225
225
|
if (!videoTrack) {
|
|
226
226
|
throw new Error("No video track in index");
|
|
227
227
|
}
|
|
228
|
+
console.log("[OnDemandVideoSession] decodeChunks start:", {
|
|
229
|
+
clipId: this.clipId,
|
|
230
|
+
chunkCount: chunks.length,
|
|
231
|
+
firstChunkType: chunks[0]?.type,
|
|
232
|
+
codec: videoTrack.codec,
|
|
233
|
+
dimensions: `${videoTrack.width}x${videoTrack.height}`
|
|
234
|
+
});
|
|
228
235
|
this.decoder = new VideoChunkDecoder(`ondemand-${this.clipId}`, {
|
|
229
236
|
codec: videoTrack.codec,
|
|
230
237
|
width: videoTrack.width,
|
|
@@ -256,7 +263,19 @@ class OnDemandVideoSession {
|
|
|
256
263
|
this.decodedFrames.push(value);
|
|
257
264
|
}
|
|
258
265
|
}
|
|
266
|
+
console.log("[OnDemandVideoSession] decodeChunks complete:", {
|
|
267
|
+
clipId: this.clipId,
|
|
268
|
+
inputChunks: chunks.length,
|
|
269
|
+
outputFrames: this.decodedFrames.length,
|
|
270
|
+
aborted: this.aborted
|
|
271
|
+
});
|
|
259
272
|
} catch (error) {
|
|
273
|
+
console.error("[OnDemandVideoSession] decodeChunks error:", {
|
|
274
|
+
clipId: this.clipId,
|
|
275
|
+
error: error instanceof Error ? error.message : String(error),
|
|
276
|
+
aborted: this.aborted,
|
|
277
|
+
decodedSoFar: this.decodedFrames.length
|
|
278
|
+
});
|
|
260
279
|
if (this.aborted) return;
|
|
261
280
|
throw error;
|
|
262
281
|
} finally {
|
|
@@ -335,7 +354,10 @@ class OnDemandVideoSession {
|
|
|
335
354
|
this.aborted = true;
|
|
336
355
|
if (this.decoder) {
|
|
337
356
|
if (this.decoder.state === "configured") {
|
|
338
|
-
|
|
357
|
+
try {
|
|
358
|
+
await this.decoder.flush();
|
|
359
|
+
} catch {
|
|
360
|
+
}
|
|
339
361
|
}
|
|
340
362
|
if (this.decoder.state !== "closed") {
|
|
341
363
|
await this.decoder.close();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OnDemandVideoSession.js","sources":["../../src/orchestrator/OnDemandVideoSession.ts"],"sourcesContent":["import type { CacheManager } from '../cache/CacheManager';\nimport type { MP4IndexCache } from '../cache/resource/MP4IndexCache';\nimport type { MP4Index, GOP } from '../stages/demux/types';\nimport type { TimeUs } from '../model/types';\nimport type { CompositionModel, Clip } from '../model';\nimport { VideoChunkDecoder } from '../stages/decode/VideoChunkDecoder';\nimport { binarySearchOverlapping } from '../utils/binary-search';\nimport type { ResourceLoader } from '../stages/load/ResourceLoader';\n\ninterface GOPWindowResult {\n gops: GOP[];\n byteStart: number;\n byteEnd: number;\n}\n\ninterface OnDemandVideoSessionConfig {\n clipId: string;\n resourceId: string;\n targetTimeUs: TimeUs;\n globalTimeUs: TimeUs;\n mp4IndexCache: MP4IndexCache;\n cacheManager: CacheManager;\n compositionModel: CompositionModel;\n resourceLoader: ResourceLoader;\n fps: number;\n}\n\n/**\n * OnDemandVideoSession - Main-thread on-demand decoder\n *\n * Strategy:\n * 1. Read GOP range from OPFS\n * 2. Demux using MP4Box (main thread)\n * 3. Decode using VideoDecoder (main thread)\n * 4. Write RAW VideoFrames (YUV) to L1 cache\n * 5. Dispose immediately after window completes\n *\n * Why main thread?\n * - Window is small (±2s, ~60-120 frames)\n * - Worker overhead (10-50ms) is significant for small workloads\n * - Decode is fast enough\n */\nexport class OnDemandVideoSession {\n /**\n * Static method to decode and cache first frame from extracted GOP chunks\n * Used by ResourceLoader during streaming download for fast cover rendering\n */\n static async decodeAndCacheFirstFrame(\n resourceId: string,\n chunks: EncodedVideoChunk[],\n index: MP4Index,\n clip: Clip,\n cacheManager: CacheManager,\n fps: number\n ): Promise<void> {\n if (chunks.length === 0) return;\n\n const videoTrack = index.tracks.video;\n if (!videoTrack) return;\n\n // Verify first chunk is keyframe\n const firstChunk = chunks[0];\n if (!firstChunk || firstChunk.type !== 'key') {\n console.warn('[OnDemandVideoSession] First chunk is not a keyframe, skipping');\n return;\n }\n\n try {\n // Create temporary decoder\n const decoder = new VideoChunkDecoder(`first-frame-${resourceId}`, {\n codec: videoTrack.codec,\n width: videoTrack.width,\n height: videoTrack.height,\n description: videoTrack.description,\n hardwareAcceleration: 'no-preference' as HardwareAcceleration,\n thread: 'main',\n });\n\n try {\n // Create chunk stream with ALL chunks from first GOP\n const chunkStream = new ReadableStream<EncodedVideoChunk>({\n start(controller) {\n for (const chunk of chunks) {\n controller.enqueue(chunk);\n }\n controller.close();\n },\n });\n\n // Decode all frames\n const frameStream = chunkStream.pipeThrough(decoder.createStream());\n const reader = frameStream.getReader();\n\n try {\n const frameDuration = Math.round(1_000_000 / fps);\n\n // Read and cache all decoded frames\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n if (value) {\n // Calculate global time for this frame\n const frameGlobalTime = clip.startUs + value.timestamp;\n\n cacheManager.addFrame(\n value,\n clip.id,\n frameDuration,\n clip.trackId ?? 'main',\n frameGlobalTime\n );\n }\n }\n } finally {\n reader.releaseLock();\n }\n } finally {\n await decoder.close();\n }\n } catch (error) {\n console.warn('[OnDemandVideoSession] Failed to decode first GOP:', error);\n // Don't throw - this is a best-effort optimization\n }\n }\n private readonly clipId: string;\n private readonly resourceId: string;\n private readonly mp4IndexCache: MP4IndexCache;\n private readonly cacheManager: CacheManager;\n private readonly compositionModel: CompositionModel;\n private readonly resourceLoader: ResourceLoader;\n private readonly fps: number;\n private readonly globalTimeUs: TimeUs;\n private readonly targetTimeUs: TimeUs;\n\n private decoder: VideoChunkDecoder | null = null;\n isDisposed = false;\n private aborted = false;\n private decodedFrames: VideoFrame[] = [];\n\n private constructor(config: OnDemandVideoSessionConfig) {\n this.clipId = config.clipId;\n this.resourceId = config.resourceId;\n this.mp4IndexCache = config.mp4IndexCache;\n this.cacheManager = config.cacheManager;\n this.resourceLoader = config.resourceLoader;\n this.compositionModel = config.compositionModel;\n this.fps = config.fps;\n this.globalTimeUs = config.globalTimeUs;\n this.targetTimeUs = config.targetTimeUs;\n }\n\n static async create(config: OnDemandVideoSessionConfig): Promise<OnDemandVideoSession> {\n const session = new OnDemandVideoSession(config);\n await session.init();\n return session;\n }\n\n private async init(): Promise<void> {\n // No special initialization needed for now\n }\n\n /**\n * Decode a window range, write raw frames to L1 cache\n */\n async decodeWindow(startUs: TimeUs, endUs: TimeUs): Promise<void> {\n if (this.isDisposed) {\n throw new Error('Session already disposed');\n }\n\n // Check if resource is an image (no MP4 index)\n const resource = this.compositionModel.getResource(this.resourceId);\n if (resource?.type === 'image') {\n // Image clip: handled by direct ImageBitmap access in PlaybackController\n // No need to decode anything here\n const image = await this.resourceLoader.loadImage(resource);\n if (image) {\n const frame = new VideoFrame(image, {\n timestamp: startUs,\n duration: endUs - startUs,\n });\n this.cacheManager.addFrame(\n frame,\n this.clipId,\n endUs - startUs,\n this.compositionModel.mainTrackId,\n this.globalTimeUs\n );\n }\n return;\n }\n\n // Video clip: decode from OPFS\n // Get MP4 index\n const index = this.mp4IndexCache.get(this.resourceId);\n if (!index) {\n throw new Error(`No index found for resource ${this.resourceId}`);\n }\n\n // Calculate GOP ranges needed (with binary search optimization)\n const gopWindow = this.calculateGOPRangesForWindow(index, startUs, endUs);\n if (gopWindow.gops.length === 0) {\n console.warn('[OnDemandVideoSession] No GOP ranges for window', startUs, endUs);\n return;\n }\n\n // Read GOP data from OPFS (merged byte range)\n const gopData = await this.cacheManager.readResourceRange(\n this.resourceId,\n gopWindow.byteStart,\n gopWindow.byteEnd\n );\n\n if (this.aborted) return;\n\n // Extract chunks from GOP data (direct sample index slicing)\n const chunks = await this.demuxGOPData(gopData, index, gopWindow);\n\n if (this.aborted) return;\n\n // Decode chunks to frames\n await this.decodeChunks(chunks, index);\n\n if (this.aborted) {\n for (const frame of this.decodedFrames) {\n frame.close();\n }\n this.decodedFrames = [];\n return;\n }\n\n // Write frames to L1\n await this.cacheDecodedFrames(startUs, endUs);\n }\n\n private calculateGOPRangesForWindow(\n index: MP4Index,\n startUs: TimeUs,\n endUs: TimeUs\n ): GOPWindowResult {\n if (!index.tracks.video) {\n console.warn('[OnDemandVideoSession] No video track in index');\n return { gops: [], byteStart: 0, byteEnd: 0 };\n }\n\n const { gopIndex, samples } = index.tracks.video;\n\n // Find GOP containing startUs (or the nearest keyframe before it)\n const nearestKeyframe = this.mp4IndexCache.getNearestKeyframe(this.resourceId, startUs);\n const decodeStartUs = nearestKeyframe?.timestamp ?? startUs;\n\n // Use binary search to find all overlapping GOPs\n const overlappingGOPs = binarySearchOverlapping(gopIndex, decodeStartUs, endUs, (gop, idx) => {\n const nextGOP = gopIndex[idx + 1];\n return {\n start: gop.startTimeUs,\n end: nextGOP ? nextGOP.startTimeUs : Infinity,\n };\n });\n\n if (overlappingGOPs.length === 0) {\n console.warn('[OnDemandVideoSession] No GOP ranges for window', startUs, endUs);\n return { gops: [], byteStart: 0, byteEnd: 0 };\n }\n\n // Calculate merged byte range for OPFS read\n let byteStart = Infinity;\n let byteEnd = 0;\n\n for (const gop of overlappingGOPs) {\n const startSample = samples[gop.keyframeSampleIndex];\n const endSampleIndex = gop.keyframeSampleIndex + gop.sampleCount - 1;\n const endSample = samples[endSampleIndex];\n\n if (startSample && endSample) {\n byteStart = Math.min(byteStart, startSample.byteOffset);\n byteEnd = Math.max(byteEnd, endSample.byteOffset + endSample.byteLength);\n }\n }\n\n return { gops: overlappingGOPs, byteStart, byteEnd };\n }\n\n /**\n * Extract video chunks from GOP data\n *\n * Directly use GOP sample indices to slice the samples array.\n * This is O(k) where k is the number of samples in the window,\n * vs O(n×m) for the old approach (n=all samples, m=GOP count).\n */\n private async demuxGOPData(\n data: ArrayBuffer,\n index: MP4Index,\n gopWindow: GOPWindowResult\n ): Promise<EncodedVideoChunk[]> {\n const videoTrack = index.tracks.video;\n if (!videoTrack) {\n throw new Error('No video track in index');\n }\n\n const { samples } = videoTrack;\n const chunks: EncodedVideoChunk[] = [];\n const dataView = new Uint8Array(data);\n const baseByteOffset = gopWindow.byteStart;\n\n // Direct sample index slicing - iterate only samples in the window\n for (const gop of gopWindow.gops) {\n const startIdx = gop.keyframeSampleIndex;\n const endIdx = startIdx + gop.sampleCount;\n\n // Extract samples for this GOP (direct array slice)\n for (let i = startIdx; i < endIdx; i++) {\n const sample = samples[i];\n if (!sample) continue;\n\n // Calculate relative offset within the data buffer\n const relativeOffset = sample.byteOffset - baseByteOffset;\n\n // Validate offset is within buffer\n if (relativeOffset < 0 || relativeOffset + sample.byteLength > data.byteLength) {\n console.warn('[OnDemandVideoSession] Sample outside buffer:', {\n sampleOffset: sample.byteOffset,\n sampleLength: sample.byteLength,\n baseOffset: baseByteOffset,\n relativeOffset,\n bufferLength: data.byteLength,\n });\n continue;\n }\n\n // Extract sample data\n const sampleData = dataView.slice(relativeOffset, relativeOffset + sample.byteLength);\n\n // Create EncodedVideoChunk\n const chunk = new EncodedVideoChunk({\n type: sample.isKeyframe ? 'key' : 'delta',\n timestamp: sample.timestamp,\n duration: sample.duration,\n data: sampleData,\n });\n\n chunks.push(chunk);\n }\n }\n\n return chunks;\n }\n\n private async decodeChunks(chunks: EncodedVideoChunk[], index: MP4Index): Promise<void> {\n const videoTrack = index.tracks.video;\n if (!videoTrack) {\n throw new Error('No video track in index');\n }\n\n // Create decoder (reuse VideoChunkDecoder like decodeFromL2)\n this.decoder = new VideoChunkDecoder(`ondemand-${this.clipId}`, {\n codec: videoTrack.codec,\n width: videoTrack.width,\n height: videoTrack.height,\n description: videoTrack.description,\n hardwareAcceleration: 'no-preference' as HardwareAcceleration,\n thread: 'main',\n });\n\n // Create stream from chunks\n const chunkStream = new ReadableStream<EncodedVideoChunk>({\n start: (controller) => {\n for (const chunk of chunks) {\n if (this.aborted) {\n controller.close();\n return;\n }\n controller.enqueue(chunk);\n }\n controller.close();\n },\n });\n\n // Pipe through decoder\n const frameStream = chunkStream.pipeThrough(this.decoder.createStream());\n\n // Collect decoded frames\n const reader = frameStream.getReader();\n try {\n while (true) {\n if (this.aborted) break;\n\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n this.decodedFrames.push(value);\n }\n }\n } catch (error) {\n if (this.aborted) return;\n throw error;\n } finally {\n reader.releaseLock();\n }\n }\n\n private async cacheDecodedFrames(startUs: TimeUs, endUs: TimeUs): Promise<void> {\n // Filter frames within window (include boundary frames)\n // Use original timestamp (no quantization)\n const framesToCache = this.decodedFrames.filter((f) => {\n // Exclude frames at window end boundary\n return f.timestamp >= startUs && f.timestamp < endUs;\n });\n\n for (const frame of framesToCache) {\n try {\n // Use original timestamp and duration (preserve source frame rate)\n const originalTimestamp = frame.timestamp;\n const originalDuration = frame.duration ?? Math.round(1_000_000 / this.fps);\n\n // Calculate global timestamp using original value\n const frameGlobalTime = this.globalTimeUs + (originalTimestamp - this.targetTimeUs);\n\n // Write RAW video frame to L1 with original timestamp, duration and global time\n // NOTE: addComposedFrame name is legacy, but now we store raw frames\n this.cacheManager.addFrame(\n frame,\n this.clipId,\n originalDuration,\n this.compositionModel.mainTrackId,\n frameGlobalTime\n );\n\n // Don't close frame here - it's now owned by L1 cache\n // (CacheManager.addComposedFrame clones it or takes ownership)\n } catch (error) {\n console.error('[OnDemandVideoSession] Cache error:', error);\n frame.close();\n }\n }\n\n // Clean up decoded frames that weren't cached\n for (const frame of this.decodedFrames) {\n if (!framesToCache.includes(frame)) {\n frame.close();\n }\n }\n this.decodedFrames = [];\n }\n\n /**\n * Fast decode single keyframe for immediate seek preview\n * Returns the decoded keyframe timestamp\n */\n async decodeKeyframe(targetTimeUs: TimeUs): Promise<TimeUs | null> {\n if (this.isDisposed || this.aborted) return null;\n\n const index = this.mp4IndexCache.get(this.resourceId);\n if (!index || !index.tracks.video) return null;\n\n // Find nearest keyframe\n const keyframeSample = this.mp4IndexCache.getNearestKeyframe(this.resourceId, targetTimeUs);\n if (!keyframeSample) return null;\n\n // Read only the keyframe bytes from OPFS\n const keyframeData = await this.cacheManager.readResourceRange(\n this.resourceId,\n keyframeSample.byteOffset,\n keyframeSample.byteOffset + keyframeSample.byteLength\n );\n\n if (this.aborted) return null;\n\n // Extract keyframe chunk\n const chunk = new EncodedVideoChunk({\n type: 'key',\n timestamp: keyframeSample.timestamp,\n duration: keyframeSample.duration,\n data: keyframeData,\n });\n\n if (this.aborted) return null;\n\n // Decode single keyframe\n await this.decodeChunks([chunk], index);\n\n if (this.aborted || this.decodedFrames.length === 0) return null;\n\n // Cache the keyframe\n const frame = this.decodedFrames[0];\n if (!frame) return null;\n\n const frameDuration = frame.duration ?? Math.round(1_000_000 / this.fps);\n const frameGlobalTime = this.globalTimeUs + (frame.timestamp - this.targetTimeUs);\n\n this.cacheManager.addFrame(\n frame,\n this.clipId,\n frameDuration,\n this.compositionModel.mainTrackId,\n frameGlobalTime\n );\n\n this.decodedFrames = [];\n\n return keyframeSample.timestamp;\n }\n\n async dispose(): Promise<void> {\n if (this.isDisposed) return;\n\n this.aborted = true;\n\n if (this.decoder) {\n if (this.decoder.state === 'configured') {\n await this.decoder.reset();\n }\n\n if (this.decoder.state !== 'closed') {\n await this.decoder.close();\n }\n this.decoder = null;\n }\n\n for (const frame of this.decodedFrames) {\n frame.close();\n }\n this.decodedFrames = [];\n\n this.isDisposed = true;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"OnDemandVideoSession.js","sources":["../../src/orchestrator/OnDemandVideoSession.ts"],"sourcesContent":["import type { CacheManager } from '../cache/CacheManager';\nimport type { MP4IndexCache } from '../cache/resource/MP4IndexCache';\nimport type { MP4Index, GOP } from '../stages/demux/types';\nimport type { TimeUs } from '../model/types';\nimport type { CompositionModel, Clip } from '../model';\nimport { VideoChunkDecoder } from '../stages/decode/VideoChunkDecoder';\nimport { binarySearchOverlapping } from '../utils/binary-search';\nimport type { ResourceLoader } from '../stages/load/ResourceLoader';\n\ninterface GOPWindowResult {\n gops: GOP[];\n byteStart: number;\n byteEnd: number;\n}\n\ninterface OnDemandVideoSessionConfig {\n clipId: string;\n resourceId: string;\n targetTimeUs: TimeUs;\n globalTimeUs: TimeUs;\n mp4IndexCache: MP4IndexCache;\n cacheManager: CacheManager;\n compositionModel: CompositionModel;\n resourceLoader: ResourceLoader;\n fps: number;\n}\n\n/**\n * OnDemandVideoSession - Main-thread on-demand decoder\n *\n * Strategy:\n * 1. Read GOP range from OPFS\n * 2. Demux using MP4Box (main thread)\n * 3. Decode using VideoDecoder (main thread)\n * 4. Write RAW VideoFrames (YUV) to L1 cache\n * 5. Dispose immediately after window completes\n *\n * Why main thread?\n * - Window is small (±2s, ~60-120 frames)\n * - Worker overhead (10-50ms) is significant for small workloads\n * - Decode is fast enough\n */\nexport class OnDemandVideoSession {\n /**\n * Static method to decode and cache first frame from extracted GOP chunks\n * Used by ResourceLoader during streaming download for fast cover rendering\n */\n static async decodeAndCacheFirstFrame(\n resourceId: string,\n chunks: EncodedVideoChunk[],\n index: MP4Index,\n clip: Clip,\n cacheManager: CacheManager,\n fps: number\n ): Promise<void> {\n if (chunks.length === 0) return;\n\n const videoTrack = index.tracks.video;\n if (!videoTrack) return;\n\n // Verify first chunk is keyframe\n const firstChunk = chunks[0];\n if (!firstChunk || firstChunk.type !== 'key') {\n console.warn('[OnDemandVideoSession] First chunk is not a keyframe, skipping');\n return;\n }\n\n try {\n // Create temporary decoder\n const decoder = new VideoChunkDecoder(`first-frame-${resourceId}`, {\n codec: videoTrack.codec,\n width: videoTrack.width,\n height: videoTrack.height,\n description: videoTrack.description,\n hardwareAcceleration: 'no-preference' as HardwareAcceleration,\n thread: 'main',\n });\n\n try {\n // Create chunk stream with ALL chunks from first GOP\n const chunkStream = new ReadableStream<EncodedVideoChunk>({\n start(controller) {\n for (const chunk of chunks) {\n controller.enqueue(chunk);\n }\n controller.close();\n },\n });\n\n // Decode all frames\n const frameStream = chunkStream.pipeThrough(decoder.createStream());\n const reader = frameStream.getReader();\n\n try {\n const frameDuration = Math.round(1_000_000 / fps);\n\n // Read and cache all decoded frames\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n if (value) {\n // Calculate global time for this frame\n const frameGlobalTime = clip.startUs + value.timestamp;\n\n cacheManager.addFrame(\n value,\n clip.id,\n frameDuration,\n clip.trackId ?? 'main',\n frameGlobalTime\n );\n }\n }\n } finally {\n reader.releaseLock();\n }\n } finally {\n await decoder.close();\n }\n } catch (error) {\n console.warn('[OnDemandVideoSession] Failed to decode first GOP:', error);\n // Don't throw - this is a best-effort optimization\n }\n }\n private readonly clipId: string;\n private readonly resourceId: string;\n private readonly mp4IndexCache: MP4IndexCache;\n private readonly cacheManager: CacheManager;\n private readonly compositionModel: CompositionModel;\n private readonly resourceLoader: ResourceLoader;\n private readonly fps: number;\n private readonly globalTimeUs: TimeUs;\n private readonly targetTimeUs: TimeUs;\n\n private decoder: VideoChunkDecoder | null = null;\n isDisposed = false;\n private aborted = false;\n private decodedFrames: VideoFrame[] = [];\n\n private constructor(config: OnDemandVideoSessionConfig) {\n this.clipId = config.clipId;\n this.resourceId = config.resourceId;\n this.mp4IndexCache = config.mp4IndexCache;\n this.cacheManager = config.cacheManager;\n this.resourceLoader = config.resourceLoader;\n this.compositionModel = config.compositionModel;\n this.fps = config.fps;\n this.globalTimeUs = config.globalTimeUs;\n this.targetTimeUs = config.targetTimeUs;\n }\n\n static async create(config: OnDemandVideoSessionConfig): Promise<OnDemandVideoSession> {\n const session = new OnDemandVideoSession(config);\n await session.init();\n return session;\n }\n\n private async init(): Promise<void> {\n // No special initialization needed for now\n }\n\n /**\n * Decode a window range, write raw frames to L1 cache\n */\n async decodeWindow(startUs: TimeUs, endUs: TimeUs): Promise<void> {\n if (this.isDisposed) {\n throw new Error('Session already disposed');\n }\n\n // Check if resource is an image (no MP4 index)\n const resource = this.compositionModel.getResource(this.resourceId);\n if (resource?.type === 'image') {\n // Image clip: handled by direct ImageBitmap access in PlaybackController\n // No need to decode anything here\n const image = await this.resourceLoader.loadImage(resource);\n if (image) {\n const frame = new VideoFrame(image, {\n timestamp: startUs,\n duration: endUs - startUs,\n });\n this.cacheManager.addFrame(\n frame,\n this.clipId,\n endUs - startUs,\n this.compositionModel.mainTrackId,\n this.globalTimeUs\n );\n }\n return;\n }\n\n // Video clip: decode from OPFS\n // Get MP4 index\n const index = this.mp4IndexCache.get(this.resourceId);\n if (!index) {\n throw new Error(`No index found for resource ${this.resourceId}`);\n }\n\n // Calculate GOP ranges needed (with binary search optimization)\n const gopWindow = this.calculateGOPRangesForWindow(index, startUs, endUs);\n if (gopWindow.gops.length === 0) {\n console.warn('[OnDemandVideoSession] No GOP ranges for window', startUs, endUs);\n return;\n }\n\n // Read GOP data from OPFS (merged byte range)\n const gopData = await this.cacheManager.readResourceRange(\n this.resourceId,\n gopWindow.byteStart,\n gopWindow.byteEnd\n );\n\n if (this.aborted) return;\n\n // Extract chunks from GOP data (direct sample index slicing)\n const chunks = await this.demuxGOPData(gopData, index, gopWindow);\n\n if (this.aborted) return;\n\n // Decode chunks to frames\n await this.decodeChunks(chunks, index);\n\n if (this.aborted) {\n for (const frame of this.decodedFrames) {\n frame.close();\n }\n this.decodedFrames = [];\n return;\n }\n\n // Write frames to L1\n await this.cacheDecodedFrames(startUs, endUs);\n }\n\n private calculateGOPRangesForWindow(\n index: MP4Index,\n startUs: TimeUs,\n endUs: TimeUs\n ): GOPWindowResult {\n if (!index.tracks.video) {\n console.warn('[OnDemandVideoSession] No video track in index');\n return { gops: [], byteStart: 0, byteEnd: 0 };\n }\n\n const { gopIndex, samples } = index.tracks.video;\n\n // Find GOP containing startUs (or the nearest keyframe before it)\n const nearestKeyframe = this.mp4IndexCache.getNearestKeyframe(this.resourceId, startUs);\n const decodeStartUs = nearestKeyframe?.timestamp ?? startUs;\n\n // Use binary search to find all overlapping GOPs\n const overlappingGOPs = binarySearchOverlapping(gopIndex, decodeStartUs, endUs, (gop, idx) => {\n const nextGOP = gopIndex[idx + 1];\n return {\n start: gop.startTimeUs,\n end: nextGOP ? nextGOP.startTimeUs : Infinity,\n };\n });\n\n if (overlappingGOPs.length === 0) {\n console.warn('[OnDemandVideoSession] No GOP ranges for window', startUs, endUs);\n return { gops: [], byteStart: 0, byteEnd: 0 };\n }\n\n // Calculate merged byte range for OPFS read\n let byteStart = Infinity;\n let byteEnd = 0;\n\n for (const gop of overlappingGOPs) {\n const startSample = samples[gop.keyframeSampleIndex];\n const endSampleIndex = gop.keyframeSampleIndex + gop.sampleCount - 1;\n const endSample = samples[endSampleIndex];\n\n if (startSample && endSample) {\n byteStart = Math.min(byteStart, startSample.byteOffset);\n byteEnd = Math.max(byteEnd, endSample.byteOffset + endSample.byteLength);\n }\n }\n\n return { gops: overlappingGOPs, byteStart, byteEnd };\n }\n\n /**\n * Extract video chunks from GOP data\n *\n * Directly use GOP sample indices to slice the samples array.\n * This is O(k) where k is the number of samples in the window,\n * vs O(n×m) for the old approach (n=all samples, m=GOP count).\n */\n private async demuxGOPData(\n data: ArrayBuffer,\n index: MP4Index,\n gopWindow: GOPWindowResult\n ): Promise<EncodedVideoChunk[]> {\n const videoTrack = index.tracks.video;\n if (!videoTrack) {\n throw new Error('No video track in index');\n }\n\n const { samples } = videoTrack;\n const chunks: EncodedVideoChunk[] = [];\n const dataView = new Uint8Array(data);\n const baseByteOffset = gopWindow.byteStart;\n\n // Direct sample index slicing - iterate only samples in the window\n for (const gop of gopWindow.gops) {\n const startIdx = gop.keyframeSampleIndex;\n const endIdx = startIdx + gop.sampleCount;\n\n // Extract samples for this GOP (direct array slice)\n for (let i = startIdx; i < endIdx; i++) {\n const sample = samples[i];\n if (!sample) continue;\n\n // Calculate relative offset within the data buffer\n const relativeOffset = sample.byteOffset - baseByteOffset;\n\n // Validate offset is within buffer\n if (relativeOffset < 0 || relativeOffset + sample.byteLength > data.byteLength) {\n console.warn('[OnDemandVideoSession] Sample outside buffer:', {\n sampleOffset: sample.byteOffset,\n sampleLength: sample.byteLength,\n baseOffset: baseByteOffset,\n relativeOffset,\n bufferLength: data.byteLength,\n });\n continue;\n }\n\n // Extract sample data\n const sampleData = dataView.slice(relativeOffset, relativeOffset + sample.byteLength);\n\n // Create EncodedVideoChunk\n const chunk = new EncodedVideoChunk({\n type: sample.isKeyframe ? 'key' : 'delta',\n timestamp: sample.timestamp,\n duration: sample.duration,\n data: sampleData,\n });\n\n chunks.push(chunk);\n }\n }\n\n return chunks;\n }\n\n private async decodeChunks(chunks: EncodedVideoChunk[], index: MP4Index): Promise<void> {\n const videoTrack = index.tracks.video;\n if (!videoTrack) {\n throw new Error('No video track in index');\n }\n\n console.log('[OnDemandVideoSession] decodeChunks start:', {\n clipId: this.clipId,\n chunkCount: chunks.length,\n firstChunkType: chunks[0]?.type,\n codec: videoTrack.codec,\n dimensions: `${videoTrack.width}x${videoTrack.height}`,\n });\n\n // Create decoder (reuse VideoChunkDecoder like decodeFromL2)\n this.decoder = new VideoChunkDecoder(`ondemand-${this.clipId}`, {\n codec: videoTrack.codec,\n width: videoTrack.width,\n height: videoTrack.height,\n description: videoTrack.description,\n hardwareAcceleration: 'no-preference' as HardwareAcceleration,\n thread: 'main',\n });\n\n // Create stream from chunks\n const chunkStream = new ReadableStream<EncodedVideoChunk>({\n start: (controller) => {\n for (const chunk of chunks) {\n if (this.aborted) {\n controller.close();\n return;\n }\n controller.enqueue(chunk);\n }\n controller.close();\n },\n });\n\n // Pipe through decoder\n const frameStream = chunkStream.pipeThrough(this.decoder.createStream());\n\n // Collect decoded frames\n const reader = frameStream.getReader();\n try {\n while (true) {\n if (this.aborted) break;\n\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n this.decodedFrames.push(value);\n }\n }\n\n console.log('[OnDemandVideoSession] decodeChunks complete:', {\n clipId: this.clipId,\n inputChunks: chunks.length,\n outputFrames: this.decodedFrames.length,\n aborted: this.aborted,\n });\n } catch (error) {\n console.error('[OnDemandVideoSession] decodeChunks error:', {\n clipId: this.clipId,\n error: error instanceof Error ? error.message : String(error),\n aborted: this.aborted,\n decodedSoFar: this.decodedFrames.length,\n });\n if (this.aborted) return;\n throw error;\n } finally {\n reader.releaseLock();\n }\n }\n\n private async cacheDecodedFrames(startUs: TimeUs, endUs: TimeUs): Promise<void> {\n // Filter frames within window (include boundary frames)\n // Use original timestamp (no quantization)\n const framesToCache = this.decodedFrames.filter((f) => {\n // Exclude frames at window end boundary\n return f.timestamp >= startUs && f.timestamp < endUs;\n });\n\n for (const frame of framesToCache) {\n try {\n // Use original timestamp and duration (preserve source frame rate)\n const originalTimestamp = frame.timestamp;\n const originalDuration = frame.duration ?? Math.round(1_000_000 / this.fps);\n\n // Calculate global timestamp using original value\n const frameGlobalTime = this.globalTimeUs + (originalTimestamp - this.targetTimeUs);\n\n // Write RAW video frame to L1 with original timestamp, duration and global time\n // NOTE: addComposedFrame name is legacy, but now we store raw frames\n this.cacheManager.addFrame(\n frame,\n this.clipId,\n originalDuration,\n this.compositionModel.mainTrackId,\n frameGlobalTime\n );\n\n // Don't close frame here - it's now owned by L1 cache\n // (CacheManager.addComposedFrame clones it or takes ownership)\n } catch (error) {\n console.error('[OnDemandVideoSession] Cache error:', error);\n frame.close();\n }\n }\n\n // Clean up decoded frames that weren't cached\n for (const frame of this.decodedFrames) {\n if (!framesToCache.includes(frame)) {\n frame.close();\n }\n }\n this.decodedFrames = [];\n }\n\n /**\n * Fast decode single keyframe for immediate seek preview\n * Returns the decoded keyframe timestamp\n */\n async decodeKeyframe(targetTimeUs: TimeUs): Promise<TimeUs | null> {\n if (this.isDisposed || this.aborted) return null;\n\n const index = this.mp4IndexCache.get(this.resourceId);\n if (!index || !index.tracks.video) return null;\n\n // Find nearest keyframe\n const keyframeSample = this.mp4IndexCache.getNearestKeyframe(this.resourceId, targetTimeUs);\n if (!keyframeSample) return null;\n\n // Read only the keyframe bytes from OPFS\n const keyframeData = await this.cacheManager.readResourceRange(\n this.resourceId,\n keyframeSample.byteOffset,\n keyframeSample.byteOffset + keyframeSample.byteLength\n );\n\n if (this.aborted) return null;\n\n // Extract keyframe chunk\n const chunk = new EncodedVideoChunk({\n type: 'key',\n timestamp: keyframeSample.timestamp,\n duration: keyframeSample.duration,\n data: keyframeData,\n });\n\n if (this.aborted) return null;\n\n // Decode single keyframe\n await this.decodeChunks([chunk], index);\n\n if (this.aborted || this.decodedFrames.length === 0) return null;\n\n // Cache the keyframe\n const frame = this.decodedFrames[0];\n if (!frame) return null;\n\n const frameDuration = frame.duration ?? Math.round(1_000_000 / this.fps);\n const frameGlobalTime = this.globalTimeUs + (frame.timestamp - this.targetTimeUs);\n\n this.cacheManager.addFrame(\n frame,\n this.clipId,\n frameDuration,\n this.compositionModel.mainTrackId,\n frameGlobalTime\n );\n\n this.decodedFrames = [];\n\n return keyframeSample.timestamp;\n }\n\n async dispose(): Promise<void> {\n if (this.isDisposed) return;\n\n this.aborted = true;\n\n if (this.decoder) {\n // Properly cleanup decoder: flush pending frames before close\n // Windows Media Foundation requires this to avoid decoder errors\n if (this.decoder.state === 'configured') {\n try {\n await this.decoder.flush();\n } catch {\n // Ignore flush errors during dispose\n }\n }\n\n if (this.decoder.state !== 'closed') {\n await this.decoder.close();\n }\n this.decoder = null;\n }\n\n for (const frame of this.decodedFrames) {\n frame.close();\n }\n this.decodedFrames = [];\n\n this.isDisposed = true;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -58,6 +58,11 @@ export declare class Orchestrator implements IOrchestrator {
|
|
|
58
58
|
}): Promise<boolean>;
|
|
59
59
|
dispose(): Promise<void>;
|
|
60
60
|
private buildWorkerConfigs;
|
|
61
|
+
/**
|
|
62
|
+
* Calculate default video bitrate based on resolution
|
|
63
|
+
* Optimized for social media platforms (YouTube, TikTok, WeChat, etc.)
|
|
64
|
+
*/
|
|
65
|
+
private calculateDefaultBitrate;
|
|
61
66
|
export(model: CompositionModel, options: ExportOptions): Promise<Blob>;
|
|
62
67
|
/**
|
|
63
68
|
* Preheat a specific clip's window range
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Orchestrator.d.ts","sourceRoot":"","sources":["../../src/orchestrator/Orchestrator.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"Orchestrator.d.ts","sourceRoot":"","sources":["../../src/orchestrator/Orchestrator.ts"],"names":[],"mappings":"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"}
|
|
@@ -368,18 +368,37 @@ class Orchestrator {
|
|
|
368
368
|
enableDucking: config.compose.audio.enableDucking
|
|
369
369
|
},
|
|
370
370
|
videoEncode: {
|
|
371
|
-
|
|
371
|
+
// Main Profile Level 4.1 - better compression efficiency for social media
|
|
372
|
+
codec: "avc1.4D0029",
|
|
372
373
|
width: defaultCanvasWidth,
|
|
373
374
|
height: defaultCanvasHeight,
|
|
374
|
-
bitrate: config.encode.video.bitrateKbps ? config.encode.video.bitrateKbps * 1e3 :
|
|
375
|
+
bitrate: config.encode.video.bitrateKbps ? config.encode.video.bitrateKbps * 1e3 : this.calculateDefaultBitrate(defaultCanvasWidth, defaultCanvasHeight),
|
|
375
376
|
framerate: targetFps,
|
|
376
377
|
latencyMode: "quality",
|
|
377
378
|
bitrateMode: "variable",
|
|
378
379
|
hardwareAcceleration: "no-preference",
|
|
380
|
+
// Default 1 second keyframe interval for better social media compatibility
|
|
381
|
+
keyFrameInterval: config.encode.video.keyIntervalS ? Math.round(config.encode.video.keyIntervalS * targetFps) : targetFps,
|
|
379
382
|
...config.encode.video
|
|
380
383
|
}
|
|
381
384
|
};
|
|
382
385
|
}
|
|
386
|
+
/**
|
|
387
|
+
* Calculate default video bitrate based on resolution
|
|
388
|
+
* Optimized for social media platforms (YouTube, TikTok, WeChat, etc.)
|
|
389
|
+
*/
|
|
390
|
+
calculateDefaultBitrate(width, height) {
|
|
391
|
+
const pixels = width * height;
|
|
392
|
+
if (pixels <= 921600) {
|
|
393
|
+
return 5e6;
|
|
394
|
+
} else if (pixels <= 2073600) {
|
|
395
|
+
return 8e6;
|
|
396
|
+
} else if (pixels <= 3686400) {
|
|
397
|
+
return 16e6;
|
|
398
|
+
} else {
|
|
399
|
+
return 25e6;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
383
402
|
async export(model, options) {
|
|
384
403
|
return this.exportScheduler.execute(model, options);
|
|
385
404
|
}
|