@meframe/core 0.4.3 → 0.4.5
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/model/types.d.ts +6 -0
- package/dist/model/types.d.ts.map +1 -1
- package/dist/model/types.js +14 -1
- package/dist/model/types.js.map +1 -1
- package/dist/orchestrator/AudioPreviewSession.d.ts.map +1 -1
- package/dist/orchestrator/AudioPreviewSession.js +3 -3
- package/dist/orchestrator/AudioPreviewSession.js.map +1 -1
- package/dist/orchestrator/AudioWindowPreparer.d.ts.map +1 -1
- package/dist/orchestrator/AudioWindowPreparer.js +6 -3
- package/dist/orchestrator/AudioWindowPreparer.js.map +1 -1
- package/dist/orchestrator/CompositionPlanner.d.ts.map +1 -1
- package/dist/orchestrator/CompositionPlanner.js +4 -2
- package/dist/orchestrator/CompositionPlanner.js.map +1 -1
- package/dist/orchestrator/ExportScheduler.d.ts.map +1 -1
- package/dist/orchestrator/ExportScheduler.js +4 -2
- package/dist/orchestrator/ExportScheduler.js.map +1 -1
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +18 -11
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/orchestrator/VideoWindowDecodeSession.d.ts +3 -0
- package/dist/orchestrator/VideoWindowDecodeSession.d.ts.map +1 -1
- package/dist/orchestrator/VideoWindowDecodeSession.js +41 -1
- package/dist/orchestrator/VideoWindowDecodeSession.js.map +1 -1
- package/dist/stages/compose/FrameRateConverter.d.ts +2 -1
- package/dist/stages/compose/FrameRateConverter.d.ts.map +1 -1
- package/dist/stages/compose/OfflineAudioMixer.d.ts.map +1 -1
- package/dist/stages/compose/OfflineAudioMixer.js +9 -3
- package/dist/stages/compose/OfflineAudioMixer.js.map +1 -1
- package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.js +58 -2
- package/dist/stages/load/ResourceLoader.js.map +1 -1
- package/dist/utils/loop-utils.d.ts +2 -0
- package/dist/utils/loop-utils.d.ts.map +1 -1
- package/dist/utils/loop-utils.js +5 -2
- package/dist/utils/loop-utils.js.map +1 -1
- package/dist/workers/stages/export/{export.worker.DCStS1mL.js → export.worker.Cw9iPvkh.js} +27 -17
- package/dist/workers/stages/export/{export.worker.DCStS1mL.js.map → export.worker.Cw9iPvkh.js.map} +1 -1
- package/dist/workers/worker-manifest.json +1 -1
- package/package.json +1 -1
package/dist/model/types.d.ts
CHANGED
|
@@ -53,6 +53,11 @@ interface BaseClip {
|
|
|
53
53
|
export interface VideoClip extends BaseClip {
|
|
54
54
|
trackKind: 'video';
|
|
55
55
|
resourceId: string;
|
|
56
|
+
/**
|
|
57
|
+
* Timeline speed: source microseconds consumed per timeline microsecond (1 = normal).
|
|
58
|
+
* Effective source span for the clip is durationUs * playbackRate from trimStartUs.
|
|
59
|
+
*/
|
|
60
|
+
playbackRate?: number;
|
|
56
61
|
renderConfig?: ClipRenderConfig;
|
|
57
62
|
audioConfig?: {
|
|
58
63
|
volume?: number;
|
|
@@ -244,5 +249,6 @@ export declare function isCaptionClip(clip: Clip): clip is CaptionClip;
|
|
|
244
249
|
export declare function isFxClip(clip: Clip): clip is FxClip;
|
|
245
250
|
export declare function hasResourceId(clip: Clip): clip is VideoClip | AudioClip;
|
|
246
251
|
export declare function hasAudioConfig(clip: Clip): clip is VideoClip | AudioClip;
|
|
252
|
+
export declare function videoClipPlaybackRate(clip: Clip): number;
|
|
247
253
|
export {};
|
|
248
254
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/model/types.ts"],"names":[],"mappings":"AACA,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC;AAG5B,eAAO,MAAM,uBAAuB,UAAY,CAAC;AACjD,eAAO,MAAM,4BAA4B,OAAQ,CAAC;AAGlD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,KAAK,CAAC;IACf,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAEpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,YAAY,CAAC;IAE5B,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAGD,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,IAAI,CAAC;IACvD,KAAK,EAAE,IAAI,EAAE,CAAC;IAEd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,CAAC,EAAE,WAAW,EAAE,CAAC;CAC9B;AAKD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAED,UAAU,QAAQ;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAE3B,YAAY,CAAC,EAAE,UAAU,CAAC;IAC1B,aAAa,CAAC,EAAE,UAAU,CAAC;IAE3B,QAAQ,CAAC,EAAE;QACT,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;QACzC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IAGF,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,SAAU,SAAQ,QAAQ;IACzC,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/model/types.ts"],"names":[],"mappings":"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"}
|
package/dist/model/types.js
CHANGED
|
@@ -10,10 +10,23 @@ function hasResourceId(clip) {
|
|
|
10
10
|
function hasAudioConfig(clip) {
|
|
11
11
|
return isVideoClip(clip) || isAudioClip(clip);
|
|
12
12
|
}
|
|
13
|
+
const PLAYBACK_RATE_MIN = 0.05;
|
|
14
|
+
const PLAYBACK_RATE_MAX = 32;
|
|
15
|
+
function videoClipPlaybackRate(clip) {
|
|
16
|
+
if (!isVideoClip(clip)) {
|
|
17
|
+
return 1;
|
|
18
|
+
}
|
|
19
|
+
const r = clip.playbackRate;
|
|
20
|
+
if (typeof r !== "number" || !Number.isFinite(r) || r <= 0) {
|
|
21
|
+
return 1;
|
|
22
|
+
}
|
|
23
|
+
return Math.min(Math.max(r, PLAYBACK_RATE_MIN), PLAYBACK_RATE_MAX);
|
|
24
|
+
}
|
|
13
25
|
export {
|
|
14
26
|
hasAudioConfig,
|
|
15
27
|
hasResourceId,
|
|
16
28
|
isAudioClip,
|
|
17
|
-
isVideoClip
|
|
29
|
+
isVideoClip,
|
|
30
|
+
videoClipPlaybackRate
|
|
18
31
|
};
|
|
19
32
|
//# sourceMappingURL=types.js.map
|
package/dist/model/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sources":["../../src/model/types.ts"],"sourcesContent":["// All time values in microseconds (µs)\nexport type TimeUs = number; // 1 second = 1_000_000 µs\n\n// Helper constants\nexport const MICROSECONDS_PER_SECOND = 1_000_000;\nexport const MICROSECONDS_PER_MILLISECOND = 1_000;\n\n// ────── Root Object ──────\nexport interface CompositionModelData {\n version: '1.0';\n fps: 24 | 25 | 30 | 60;\n durationUs: TimeUs;\n tracks: Track[];\n resources: Record<string, Resource>;\n\n mainTrackId?: string;\n renderConfig?: RenderConfig;\n\n ext?: Record<string, unknown>;\n}\n\nexport interface RenderConfig {\n width: number;\n height: number;\n backgroundColor?: string;\n}\n\n// ────── Track ──────\nexport interface Track {\n id: string;\n kind: 'video' | 'audio' | 'caption' | 'overlay' | 'fx';\n clips: Clip[];\n\n effects?: Effect[];\n duckingRules?: DuckingRule[];\n}\n\n// ────── Clip ──────\n\n// Clip-level render configuration for video/image resources\nexport interface ClipRenderConfig {\n width?: number | string; // Pixels or percentage (e.g., \"10%\")\n height?: number | string;\n}\n\ninterface BaseClip {\n id: string;\n startUs: TimeUs;\n durationUs: TimeUs;\n trackId?: string;\n\n trimStartUs?: TimeUs;\n /**\n * Loop the underlying media content to fill this clip's duration.\n *\n * - AudioClip: loop audio samples\n * - VideoClip: (future) loop video frames (and embedded audio) together\n */\n loop?: boolean;\n\n effects?: Effect[];\n attachments?: Attachment[];\n\n transitionIn?: Transition;\n transitionOut?: Transition;\n\n metadata?: {\n purpose?: 'caption' | 'overlay' | 'mask';\n [key: string]: unknown;\n };\n\n // Internal: temporary field for tracking old resourceId during patch operations\n oldResourceId?: string;\n}\n\nexport interface VideoClip extends BaseClip {\n trackKind: 'video';\n resourceId: string;\n\n // Render configuration (size, positioning strategy)\n renderConfig?: ClipRenderConfig;\n\n // Audio configuration for video's original sound\n audioConfig?: {\n volume?: number; // 0.0-1.0, default 1.0\n muted?: boolean; // default false\n };\n}\n\nexport interface AudioClip extends BaseClip {\n trackKind: 'audio';\n resourceId: string;\n\n // Audio configuration\n audioConfig?: {\n volume?: number; // 0.0-1.0, default 1.0\n muted?: boolean; // default false\n };\n}\n\nexport interface CaptionClip extends BaseClip {\n trackKind: 'caption';\n text: string;\n localeCode?: string;\n fontTemplate?: string;\n fontFamily?: string;\n wordTimings?: Array<{\n text: string;\n startUs: TimeUs;\n endUs: TimeUs;\n }>;\n animation?: {\n type: 'none' | 'fade' | 'wordByWord' | 'characterKTV' | 'wordByWordFancy' | 'wordByWordSlideUp';\n [key: string]: unknown;\n };\n letterCase?: 'upper' | 'lower' | 'none';\n}\n\nexport interface FxClip extends BaseClip {\n trackKind: 'fx';\n}\n\nexport interface OverlayClip extends BaseClip {\n trackKind: 'overlay';\n resourceId: string;\n\n // Render configuration (size, positioning strategy)\n renderConfig?: ClipRenderConfig;\n\n // Optional overlay-specific config\n opacity?: number; // 0.0-1.0, default 1.0\n}\n\nexport type Clip = VideoClip | AudioClip | CaptionClip | OverlayClip | FxClip;\n\n// ────── Resource ──────\nexport interface Resource {\n id: string;\n type: 'video' | 'image' | 'audio' | 'json' | string;\n uri: string;\n metadata?: Record<string, unknown>;\n clipIds?: string[];\n // Runtime state maintained by engine\n state?: 'pending' | 'loading' | 'ready' | 'error';\n // Runtime error info maintained by engine (best-effort, non-persistent)\n error?: {\n code: string;\n message: string;\n terminal?: boolean;\n };\n}\n\n// ────── Common Structures ──────\nexport interface Effect {\n id: string;\n effectType: 'filter' | 'lut' | 'animation' | string;\n params?: Record<string, unknown>;\n}\n\n// Animation effect (effectType: 'animation')\nexport interface AnimationEffect extends Effect {\n effectType: 'animation';\n params: {\n position: { x: number; y: number };\n keyframes: AnimationKeyframe[];\n };\n}\n\nexport interface AnimationKeyframe {\n time: number; // Relative to clip.startUs (microseconds)\n transform?: Transform2D;\n opacity?: number;\n easing?: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out';\n}\n\nexport interface Transform2D {\n x?: number; // Relative offset\n y?: number;\n scaleX?: number;\n scaleY?: number;\n rotation?: number; // Degrees\n anchorX?: number; // Rotation center x (0-1)\n anchorY?: number; // Rotation center y (0-1)\n}\n\nexport interface Transition {\n id: string;\n transitionType: 'fade' | 'wipe' | 'slide' | string;\n durationUs: TimeUs;\n curve?: 'linear' | 'ease-in' | 'ease-out' | string;\n params?: Record<string, unknown>;\n}\n\nexport interface Attachment {\n id: string;\n kind: 'caption' | 'overlay' | 'mask' | string;\n startUs: TimeUs;\n durationUs: TimeUs;\n data: Record<string, unknown>;\n}\n\nexport interface CaptionAttachmentData {\n text: string;\n localeCode?: string;\n fontTemplate?: string;\n fontFamily?: string;\n wordTimings?: Array<{\n text: string;\n startUs: TimeUs;\n endUs: TimeUs;\n }>;\n animation?: {\n type: 'none' | 'fade' | 'wordByWord' | 'characterKTV' | 'wordByWordFancy' | 'wordByWordSlideUp';\n [key: string]: unknown;\n };\n letterCase?: 'upper' | 'lower' | 'none';\n overlayClipStartUs?: TimeUs;\n mainClipStartUs?: TimeUs;\n}\n\nexport interface DuckingRule {\n targetTrackKind: 'voice' | 'audio' | string;\n ratio: number;\n attackMs: number;\n releaseMs: number;\n}\n\n// ────── Patch System ──────\nexport interface CompositionPatch {\n operations: PatchOperation[];\n metadata?: {\n timestamp: number;\n source?: string;\n version?: string;\n };\n}\n\nexport type PatchOperation =\n | TrackOperation\n | ClipOperation\n | ResourceOperation\n | AttachmentOperation\n | TransitionOperation\n | EffectOperation\n | RenderConfigOperation;\n\n// Track operations\nexport interface TrackOperation {\n type: 'addTrack' | 'updateTrack' | 'removeTrack';\n trackId?: string;\n track?: Partial<Track>;\n}\n\n// Clip operations\nexport interface ClipOperation {\n type: 'addClip' | 'updateClip' | 'removeClip' | 'moveClip';\n trackId: string;\n clipId?: string;\n clip?: Partial<Clip>;\n targetTrackId?: string;\n targetStartUs?: TimeUs;\n}\n\n// Resource operations\nexport interface ResourceOperation {\n type: 'addResource' | 'updateResource' | 'removeResource';\n resourceId: string;\n resource?: Partial<Resource>;\n}\n\n// Attachment operations\nexport interface AttachmentOperation {\n type: 'addAttachment' | 'updateAttachment' | 'removeAttachment';\n trackId: string;\n clipId: string;\n attachmentId?: string;\n attachment?: Partial<Attachment>;\n}\n\n// Transition operations\nexport interface TransitionOperation {\n type: 'addTransition' | 'updateTransition' | 'removeTransition';\n trackId: string;\n clipId: string;\n position: 'in' | 'out';\n transition?: Partial<Transition>;\n}\n\n// Render config operations\nexport interface RenderConfigOperation {\n type: 'updateRenderConfig';\n renderConfig?: Partial<RenderConfig>;\n}\n\n// Effect operations\nexport interface EffectOperation {\n type: 'addEffect' | 'updateEffect' | 'removeEffect';\n targetType: 'track' | 'clip';\n targetId: string;\n effectId?: string;\n effect?: Partial<Effect>;\n}\n\n// ────── Dirty Range ──────\nexport interface DirtyRange {\n trackId: string;\n startUs: TimeUs;\n endUs: TimeUs;\n reason: string;\n}\n\n// ────── Validation ──────\nexport interface ValidationError {\n path: string;\n message: string;\n value: any;\n}\n\n// ────── Type Guards ──────\nexport function isVideoClip(clip: Clip): clip is VideoClip {\n return clip.trackKind === 'video';\n}\n\nexport function isAudioClip(clip: Clip): clip is AudioClip {\n return clip.trackKind === 'audio';\n}\n\nexport function isCaptionClip(clip: Clip): clip is CaptionClip {\n return clip.trackKind === 'caption';\n}\n\nexport function isFxClip(clip: Clip): clip is FxClip {\n return clip.trackKind === 'fx';\n}\n\nexport function hasResourceId(clip: Clip): clip is VideoClip | AudioClip {\n return isVideoClip(clip) || isAudioClip(clip);\n}\n\nexport function hasAudioConfig(clip: Clip): clip is VideoClip | AudioClip {\n return isVideoClip(clip) || isAudioClip(clip);\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sources":["../../src/model/types.ts"],"sourcesContent":["// All time values in microseconds (µs)\nexport type TimeUs = number; // 1 second = 1_000_000 µs\n\n// Helper constants\nexport const MICROSECONDS_PER_SECOND = 1_000_000;\nexport const MICROSECONDS_PER_MILLISECOND = 1_000;\n\n// ────── Root Object ──────\nexport interface CompositionModelData {\n version: '1.0';\n fps: 24 | 25 | 30 | 60;\n durationUs: TimeUs;\n tracks: Track[];\n resources: Record<string, Resource>;\n\n mainTrackId?: string;\n renderConfig?: RenderConfig;\n\n ext?: Record<string, unknown>;\n}\n\nexport interface RenderConfig {\n width: number;\n height: number;\n backgroundColor?: string;\n}\n\n// ────── Track ──────\nexport interface Track {\n id: string;\n kind: 'video' | 'audio' | 'caption' | 'overlay' | 'fx';\n clips: Clip[];\n\n effects?: Effect[];\n duckingRules?: DuckingRule[];\n}\n\n// ────── Clip ──────\n\n// Clip-level render configuration for video/image resources\nexport interface ClipRenderConfig {\n width?: number | string; // Pixels or percentage (e.g., \"10%\")\n height?: number | string;\n}\n\ninterface BaseClip {\n id: string;\n startUs: TimeUs;\n durationUs: TimeUs;\n trackId?: string;\n\n trimStartUs?: TimeUs;\n /**\n * Loop the underlying media content to fill this clip's duration.\n *\n * - AudioClip: loop audio samples\n * - VideoClip: (future) loop video frames (and embedded audio) together\n */\n loop?: boolean;\n\n effects?: Effect[];\n attachments?: Attachment[];\n\n transitionIn?: Transition;\n transitionOut?: Transition;\n\n metadata?: {\n purpose?: 'caption' | 'overlay' | 'mask';\n [key: string]: unknown;\n };\n\n // Internal: temporary field for tracking old resourceId during patch operations\n oldResourceId?: string;\n}\n\nexport interface VideoClip extends BaseClip {\n trackKind: 'video';\n resourceId: string;\n\n /**\n * Timeline speed: source microseconds consumed per timeline microsecond (1 = normal).\n * Effective source span for the clip is durationUs * playbackRate from trimStartUs.\n */\n playbackRate?: number;\n\n // Render configuration (size, positioning strategy)\n renderConfig?: ClipRenderConfig;\n\n // Audio configuration for video's original sound\n audioConfig?: {\n volume?: number; // 0.0-1.0, default 1.0\n muted?: boolean; // default false\n };\n}\n\nexport interface AudioClip extends BaseClip {\n trackKind: 'audio';\n resourceId: string;\n\n // Audio configuration\n audioConfig?: {\n volume?: number; // 0.0-1.0, default 1.0\n muted?: boolean; // default false\n };\n}\n\nexport interface CaptionClip extends BaseClip {\n trackKind: 'caption';\n text: string;\n localeCode?: string;\n fontTemplate?: string;\n fontFamily?: string;\n wordTimings?: Array<{\n text: string;\n startUs: TimeUs;\n endUs: TimeUs;\n }>;\n animation?: {\n type: 'none' | 'fade' | 'wordByWord' | 'characterKTV' | 'wordByWordFancy' | 'wordByWordSlideUp';\n [key: string]: unknown;\n };\n letterCase?: 'upper' | 'lower' | 'none';\n}\n\nexport interface FxClip extends BaseClip {\n trackKind: 'fx';\n}\n\nexport interface OverlayClip extends BaseClip {\n trackKind: 'overlay';\n resourceId: string;\n\n // Render configuration (size, positioning strategy)\n renderConfig?: ClipRenderConfig;\n\n // Optional overlay-specific config\n opacity?: number; // 0.0-1.0, default 1.0\n}\n\nexport type Clip = VideoClip | AudioClip | CaptionClip | OverlayClip | FxClip;\n\n// ────── Resource ──────\nexport interface Resource {\n id: string;\n type: 'video' | 'image' | 'audio' | 'json' | string;\n uri: string;\n metadata?: Record<string, unknown>;\n clipIds?: string[];\n // Runtime state maintained by engine\n state?: 'pending' | 'loading' | 'ready' | 'error';\n // Runtime error info maintained by engine (best-effort, non-persistent)\n error?: {\n code: string;\n message: string;\n terminal?: boolean;\n };\n}\n\n// ────── Common Structures ──────\nexport interface Effect {\n id: string;\n effectType: 'filter' | 'lut' | 'animation' | string;\n params?: Record<string, unknown>;\n}\n\n// Animation effect (effectType: 'animation')\nexport interface AnimationEffect extends Effect {\n effectType: 'animation';\n params: {\n position: { x: number; y: number };\n keyframes: AnimationKeyframe[];\n };\n}\n\nexport interface AnimationKeyframe {\n time: number; // Relative to clip.startUs (microseconds)\n transform?: Transform2D;\n opacity?: number;\n easing?: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out';\n}\n\nexport interface Transform2D {\n x?: number; // Relative offset\n y?: number;\n scaleX?: number;\n scaleY?: number;\n rotation?: number; // Degrees\n anchorX?: number; // Rotation center x (0-1)\n anchorY?: number; // Rotation center y (0-1)\n}\n\nexport interface Transition {\n id: string;\n transitionType: 'fade' | 'wipe' | 'slide' | string;\n durationUs: TimeUs;\n curve?: 'linear' | 'ease-in' | 'ease-out' | string;\n params?: Record<string, unknown>;\n}\n\nexport interface Attachment {\n id: string;\n kind: 'caption' | 'overlay' | 'mask' | string;\n startUs: TimeUs;\n durationUs: TimeUs;\n data: Record<string, unknown>;\n}\n\nexport interface CaptionAttachmentData {\n text: string;\n localeCode?: string;\n fontTemplate?: string;\n fontFamily?: string;\n wordTimings?: Array<{\n text: string;\n startUs: TimeUs;\n endUs: TimeUs;\n }>;\n animation?: {\n type: 'none' | 'fade' | 'wordByWord' | 'characterKTV' | 'wordByWordFancy' | 'wordByWordSlideUp';\n [key: string]: unknown;\n };\n letterCase?: 'upper' | 'lower' | 'none';\n overlayClipStartUs?: TimeUs;\n mainClipStartUs?: TimeUs;\n}\n\nexport interface DuckingRule {\n targetTrackKind: 'voice' | 'audio' | string;\n ratio: number;\n attackMs: number;\n releaseMs: number;\n}\n\n// ────── Patch System ──────\nexport interface CompositionPatch {\n operations: PatchOperation[];\n metadata?: {\n timestamp: number;\n source?: string;\n version?: string;\n };\n}\n\nexport type PatchOperation =\n | TrackOperation\n | ClipOperation\n | ResourceOperation\n | AttachmentOperation\n | TransitionOperation\n | EffectOperation\n | RenderConfigOperation;\n\n// Track operations\nexport interface TrackOperation {\n type: 'addTrack' | 'updateTrack' | 'removeTrack';\n trackId?: string;\n track?: Partial<Track>;\n}\n\n// Clip operations\nexport interface ClipOperation {\n type: 'addClip' | 'updateClip' | 'removeClip' | 'moveClip';\n trackId: string;\n clipId?: string;\n clip?: Partial<Clip>;\n targetTrackId?: string;\n targetStartUs?: TimeUs;\n}\n\n// Resource operations\nexport interface ResourceOperation {\n type: 'addResource' | 'updateResource' | 'removeResource';\n resourceId: string;\n resource?: Partial<Resource>;\n}\n\n// Attachment operations\nexport interface AttachmentOperation {\n type: 'addAttachment' | 'updateAttachment' | 'removeAttachment';\n trackId: string;\n clipId: string;\n attachmentId?: string;\n attachment?: Partial<Attachment>;\n}\n\n// Transition operations\nexport interface TransitionOperation {\n type: 'addTransition' | 'updateTransition' | 'removeTransition';\n trackId: string;\n clipId: string;\n position: 'in' | 'out';\n transition?: Partial<Transition>;\n}\n\n// Render config operations\nexport interface RenderConfigOperation {\n type: 'updateRenderConfig';\n renderConfig?: Partial<RenderConfig>;\n}\n\n// Effect operations\nexport interface EffectOperation {\n type: 'addEffect' | 'updateEffect' | 'removeEffect';\n targetType: 'track' | 'clip';\n targetId: string;\n effectId?: string;\n effect?: Partial<Effect>;\n}\n\n// ────── Dirty Range ──────\nexport interface DirtyRange {\n trackId: string;\n startUs: TimeUs;\n endUs: TimeUs;\n reason: string;\n}\n\n// ────── Validation ──────\nexport interface ValidationError {\n path: string;\n message: string;\n value: any;\n}\n\n// ────── Type Guards ──────\nexport function isVideoClip(clip: Clip): clip is VideoClip {\n return clip.trackKind === 'video';\n}\n\nexport function isAudioClip(clip: Clip): clip is AudioClip {\n return clip.trackKind === 'audio';\n}\n\nexport function isCaptionClip(clip: Clip): clip is CaptionClip {\n return clip.trackKind === 'caption';\n}\n\nexport function isFxClip(clip: Clip): clip is FxClip {\n return clip.trackKind === 'fx';\n}\n\nexport function hasResourceId(clip: Clip): clip is VideoClip | AudioClip {\n return isVideoClip(clip) || isAudioClip(clip);\n}\n\nexport function hasAudioConfig(clip: Clip): clip is VideoClip | AudioClip {\n return isVideoClip(clip) || isAudioClip(clip);\n}\n\nconst PLAYBACK_RATE_MIN = 0.05;\nconst PLAYBACK_RATE_MAX = 32;\n\nexport function videoClipPlaybackRate(clip: Clip): number {\n if (!isVideoClip(clip)) {\n return 1;\n }\n const r = clip.playbackRate;\n if (typeof r !== 'number' || !Number.isFinite(r) || r <= 0) {\n return 1;\n }\n return Math.min(Math.max(r, PLAYBACK_RATE_MIN), PLAYBACK_RATE_MAX);\n}\n"],"names":[],"mappings":"AAqUO,SAAS,YAAY,MAA+B;AACzD,SAAO,KAAK,cAAc;AAC5B;AAEO,SAAS,YAAY,MAA+B;AACzD,SAAO,KAAK,cAAc;AAC5B;AAUO,SAAS,cAAc,MAA2C;AACvE,SAAO,YAAY,IAAI,KAAK,YAAY,IAAI;AAC9C;AAEO,SAAS,eAAe,MAA2C;AACxE,SAAO,YAAY,IAAI,KAAK,YAAY,IAAI;AAC9C;AAEA,MAAM,oBAAoB;AAC1B,MAAM,oBAAoB;AAEnB,SAAS,sBAAsB,MAAoB;AACxD,MAAI,CAAC,YAAY,IAAI,GAAG;AACtB,WAAO;AAAA,EACT;AACA,QAAM,IAAI,KAAK;AACf,MAAI,OAAO,MAAM,YAAY,CAAC,OAAO,SAAS,CAAC,KAAK,KAAK,GAAG;AAC1D,WAAO;AAAA,EACT;AACA,SAAO,KAAK,IAAI,KAAK,IAAI,GAAG,iBAAiB,GAAG,iBAAiB;AACnE;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioPreviewSession.d.ts","sourceRoot":"","sources":["../../src/orchestrator/AudioPreviewSession.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAG7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEjE,qBAAa,mBAAmB;IA+BlB,OAAO,CAAC,IAAI;IA9BxB,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,MAAM,CAAO;IACrB,OAAO,CAAC,YAAY,CAAO;IAC3B,OAAO,CAAC,SAAS,CAAS;IAM1B,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAA0B;IACpE,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAK;IAC9C,OAAO,CAAC,QAAQ,CAAC,0BAA0B,
|
|
1
|
+
{"version":3,"file":"AudioPreviewSession.d.ts","sourceRoot":"","sources":["../../src/orchestrator/AudioPreviewSession.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAG7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEjE,qBAAa,mBAAmB;IA+BlB,OAAO,CAAC,IAAI;IA9BxB,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,MAAM,CAAO;IACrB,OAAO,CAAC,YAAY,CAAO;IAC3B,OAAO,CAAC,SAAS,CAAS;IAM1B,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAA0B;IACpE,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAK;IAC9C,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAAO;IAClD,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAqB;IAC7D,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAQ;IAE/C,OAAO,CAAC,iBAAiB,CAAyD;IAElF,OAAO,CAAC,mBAAmB,CAA8B;IACzD,OAAO,CAAC,oBAAoB,CAAK;IACjC,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,qBAAqB,CAAK;IAClC,OAAO,CAAC,uBAAuB,CAAK;IACpC,OAAO,CAAC,yBAAyB,CAAa;IAC9C,OAAO,CAAC,qBAAqB,CAAa;IAC1C,OAAO,CAAC,sBAAsB,CAAK;IAEnC,OAAO,CAAC,uBAAuB,CAAgE;IAC/F,OAAO,CAAC,eAAe,CAAuC;gBAE1C,IAAI,EAAE;QAAE,YAAY,EAAE,YAAY,CAAC;QAAC,QAAQ,EAAE,mBAAmB,CAAA;KAAE;IAIvF,yBAAyB,IAAI,IAAI;IASjC,OAAO,CAAC,iBAAiB;IAyBnB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAqCzF,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAKhD,2CAA2C,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAgB9D,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAqB9E,YAAY,IAAI,IAAI;IAOpB;;OAEG;IACG,aAAa,CAAC,iBAAiB,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAyBzF;;OAEG;IACH,mBAAmB,IAAI,IAAI;IAM3B,KAAK,IAAI,IAAI;IAOb,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAkB/B,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAMnC,OAAO,CAAC,2BAA2B;IAcnC,OAAO,CAAC,4BAA4B;IASpC,OAAO,CAAC,kCAAkC;YAe5B,6BAA6B;YA2C7B,qBAAqB;YA2BrB,wBAAwB;IA2FtC,OAAO,CAAC,qBAAqB;CAoB9B"}
|
|
@@ -14,10 +14,10 @@ class AudioPreviewSession {
|
|
|
14
14
|
// - Always schedule audio in fixed 60s "mix blocks"
|
|
15
15
|
// - Cache 2~3 mixed AudioBuffer blocks (LRU) to accelerate seek
|
|
16
16
|
// - Schedule ahead using AudioContext clock to avoid underrun
|
|
17
|
-
PREVIEW_BLOCK_DURATION_US =
|
|
18
|
-
//
|
|
17
|
+
PREVIEW_BLOCK_DURATION_US = 12 * 1e6;
|
|
18
|
+
// 12s
|
|
19
19
|
PREVIEW_BLOCK_CACHE_SIZE = 3;
|
|
20
|
-
PREVIEW_SCHEDULE_AHEAD_SEC =
|
|
20
|
+
PREVIEW_SCHEDULE_AHEAD_SEC = 6;
|
|
21
21
|
// keep enough scheduled audio to hide mixing latency
|
|
22
22
|
PREVIEW_BUFFER_GUARD_US = 2e6;
|
|
23
23
|
// if next block isn't ready near boundary -> buffering
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioPreviewSession.js","sources":["../../src/orchestrator/AudioPreviewSession.ts"],"sourcesContent":["import type { TimeUs } from '../model/types';\nimport { OfflineAudioMixer } from '../stages/compose/OfflineAudioMixer';\nimport { AudioMixBlockCache } from '../cache/AudioMixBlockCache';\nimport type { CacheManager } from '../cache/CacheManager';\nimport type { RequestMode } from './types';\nimport type { AudioWindowPreparer } from './AudioWindowPreparer';\n\nexport class AudioPreviewSession {\n private mixer: OfflineAudioMixer;\n private audioContext: AudioContext | null = null;\n private volume = 1.0;\n private playbackRate = 1.0;\n private isPlaying = false;\n\n // Preview strategy (unified):\n // - Always schedule audio in fixed 60s \"mix blocks\"\n // - Cache 2~3 mixed AudioBuffer blocks (LRU) to accelerate seek\n // - Schedule ahead using AudioContext clock to avoid underrun\n private readonly PREVIEW_BLOCK_DURATION_US: TimeUs = 60 * 1_000_000; // 60s\n private readonly PREVIEW_BLOCK_CACHE_SIZE = 3;\n private readonly PREVIEW_SCHEDULE_AHEAD_SEC = 12.0; // keep enough scheduled audio to hide mixing latency\n private readonly PREVIEW_BUFFER_GUARD_US: TimeUs = 2_000_000; // if next block isn't ready near boundary -> buffering\n private readonly PREVIEW_BLOCK_FADE_SEC = 0.01; // 10ms fade-in/out to avoid click at boundaries\n\n private previewBlockCache = new AudioMixBlockCache(this.PREVIEW_BLOCK_CACHE_SIZE);\n\n private previewScheduleTask: Promise<void> | null = null;\n private previewScheduleToken = 0;\n private previewMixToken = 0;\n private previewNextBlockIndex = 0;\n private previewNextScheduleTime = 0; // AudioContext time\n private previewFirstBlockOffsetUs: TimeUs = 0; // seek offset within the first block\n private previewLastTimelineUs: TimeUs = 0;\n private previewLastStallWarnAt = 0; // AudioContext time (sec)\n\n private previewScheduledSources = new Set<{ source: AudioBufferSourceNode; gain: GainNode }>();\n private previewMixQueue: Promise<unknown> = Promise.resolve();\n\n constructor(private deps: { cacheManager: CacheManager; preparer: AudioWindowPreparer }) {\n this.mixer = new OfflineAudioMixer(deps.cacheManager, () => deps.preparer.getModel());\n }\n\n invalidatePreviewMixCache(): void {\n // Mixed AudioBuffer blocks embed per-clip audioConfig (volume/muted).\n // When model is replaced via setCompositionModel, these blocks must be invalidated,\n // otherwise preview may keep scheduling old AudioBuffers and volume changes won't take effect.\n this.previewBlockCache.clear();\n // Ensure any in-flight mix tasks are dropped.\n this.previewMixToken += 1;\n }\n\n private enqueuePreviewMix<T>(work: () => Promise<T>, token: number): Promise<T | null> {\n const run = () => work();\n const next = this.previewMixQueue.then(\n () => {\n // If a seek/reset happened since this task was enqueued, drop it.\n if (this.previewMixToken !== token) {\n return null;\n }\n return run();\n },\n () => {\n if (this.previewMixToken !== token) {\n return null;\n }\n return run();\n }\n );\n // Keep queue alive even if a task fails.\n this.previewMixQueue = next.then(\n () => undefined,\n () => undefined\n );\n return next as Promise<T | null>;\n }\n\n async ensureAudioForTime(timeUs: TimeUs, options?: { mode?: RequestMode }): Promise<void> {\n if (this.deps.cacheManager.isExporting) return;\n\n const model = this.deps.preparer.getModel();\n if (!model) return;\n\n const mode = options?.mode ?? 'blocking';\n\n // Preview contract:\n // - blocking: ensure the current 60s mixed block is ready (may be slow -> should be wrapped by buffering UI)\n // - probe: best-effort preheat current and next block without blocking\n const blockIndex = Math.floor(Math.max(0, timeUs) / this.PREVIEW_BLOCK_DURATION_US);\n\n if (mode === 'probe') {\n // Default: only preheat the current block.\n void this.getOrCreateMixedBlock(blockIndex);\n\n // If we're close enough to boundary (within scheduling lookahead), also preheat next block.\n const blockEndUs = (blockIndex + 1) * this.PREVIEW_BLOCK_DURATION_US;\n const remainingToBoundaryUs = blockEndUs - Math.max(0, timeUs);\n const lookaheadUs = Math.floor(this.PREVIEW_SCHEDULE_AHEAD_SEC * 1_000_000);\n if (remainingToBoundaryUs > 0 && remainingToBoundaryUs <= lookaheadUs) {\n void this.getOrCreateMixedBlock(blockIndex + 1);\n }\n return;\n }\n\n await this.getOrCreateMixedBlock(blockIndex);\n\n // If we're very close to the block boundary, also ensure the next block.\n const blockEndUs = (blockIndex + 1) * this.PREVIEW_BLOCK_DURATION_US;\n const remainingToBoundaryUs = blockEndUs - Math.max(0, timeUs);\n if (remainingToBoundaryUs > 0 && remainingToBoundaryUs <= this.PREVIEW_BUFFER_GUARD_US) {\n await this.getOrCreateMixedBlock(blockIndex + 1);\n }\n }\n\n isPreviewMixBlockCached(timeUs: TimeUs): boolean {\n const blockIndex = Math.floor(Math.max(0, timeUs) / this.PREVIEW_BLOCK_DURATION_US);\n return this.previewBlockCache.get(blockIndex) !== null;\n }\n\n shouldEnterBufferingForUpcomingPreviewAudio(timeUs: TimeUs): boolean {\n const model = this.deps.preparer.getModel();\n if (!model) return false;\n\n const clampedUs = Math.max(0, timeUs);\n const blockIndex = Math.floor(clampedUs / this.PREVIEW_BLOCK_DURATION_US);\n const nextBlockStartUs = (blockIndex + 1) * this.PREVIEW_BLOCK_DURATION_US;\n if (nextBlockStartUs >= model.durationUs) return false;\n\n const remainingToBoundaryUs = nextBlockStartUs - clampedUs;\n if (remainingToBoundaryUs > this.PREVIEW_BUFFER_GUARD_US) return false;\n\n // Probe next block readiness by checking cache at next block start time.\n return !this.isPreviewMixBlockCached(nextBlockStartUs);\n }\n\n async startPlayback(timeUs: TimeUs, audioContext: AudioContext): Promise<void> {\n this.audioContext = audioContext;\n\n // Resume AudioContext if suspended (required by modern browsers)\n if (audioContext.state === 'suspended') {\n await audioContext.resume();\n }\n\n // Ensure audio is decoded and ready (blocking mode for startup).\n await this.ensureAudioForTime(timeUs, { mode: 'blocking' });\n\n this.isPlaying = true;\n\n // Unified block scheduling: align to block index and schedule immediately.\n this.startPreviewBlockScheduling(timeUs, audioContext);\n // Schedule first block in blocking mode to avoid initial silence.\n await this.scheduleNextPreviewBlock(audioContext, this.previewScheduleToken);\n // Then keep scheduling in background.\n void this.scheduleAudio(timeUs, audioContext);\n }\n\n stopPlayback(): void {\n this.isPlaying = false;\n this.stopAllPreviewSources();\n this.cancelPreviewBlockScheduling();\n this.previewMixToken += 1;\n }\n\n /**\n * Schedule audio chunks ahead of playback cursor.\n */\n async scheduleAudio(currentTimelineUs: TimeUs, audioContext: AudioContext): Promise<void> {\n if (!this.isPlaying || !this.deps.preparer.getModel() || !this.audioContext) {\n return;\n }\n\n this.previewLastTimelineUs = currentTimelineUs;\n\n // Keep scheduling in the background to avoid blocking the render loop.\n if (this.previewScheduleTask) return;\n\n // Initialize if needed (e.g. after model switch without explicit startPlayback).\n if (this.previewNextScheduleTime === 0) {\n this.startPreviewBlockScheduling(currentTimelineUs, audioContext);\n }\n\n const token = this.previewScheduleToken;\n this.previewScheduleTask = this.runPreviewBlockSchedulingLoop(audioContext, token).finally(\n () => {\n if (this.previewScheduleToken === token) {\n this.previewScheduleTask = null;\n }\n }\n );\n }\n\n /**\n * Reset playback states (called on seek)\n */\n resetPlaybackStates(): void {\n this.stopAllPreviewSources();\n this.cancelPreviewBlockScheduling();\n this.previewMixToken += 1;\n }\n\n reset(): void {\n this.stopPlayback();\n this.deps.cacheManager.clearAudioCache();\n this.previewBlockCache.clear();\n this.deps.preparer.reset();\n }\n\n setVolume(volume: number): void {\n this.volume = volume;\n const audioContext = this.audioContext;\n if (!audioContext) return;\n\n // Apply volume to already scheduled nodes (small ramp to avoid click).\n const t = audioContext.currentTime;\n for (const { gain } of this.previewScheduledSources) {\n try {\n gain.gain.cancelScheduledValues(t);\n gain.gain.setValueAtTime(gain.gain.value, t);\n gain.gain.linearRampToValueAtTime(volume, t + 0.01);\n } catch {\n // ignore\n }\n }\n }\n\n setPlaybackRate(rate: number): void {\n this.playbackRate = rate;\n // Playback rate change requires restarting scheduling to keep sync with the timeline clock.\n this.resetPlaybackStates();\n }\n\n private startPreviewBlockScheduling(startTimelineUs: TimeUs, audioContext: AudioContext): void {\n this.cancelPreviewBlockScheduling();\n this.stopAllPreviewSources();\n\n const clampedUs = Math.max(0, startTimelineUs);\n const blockIndex = Math.floor(clampedUs / this.PREVIEW_BLOCK_DURATION_US);\n const blockStartUs = blockIndex * this.PREVIEW_BLOCK_DURATION_US;\n\n this.previewFirstBlockOffsetUs = clampedUs - blockStartUs;\n this.previewNextBlockIndex = blockIndex;\n this.previewNextScheduleTime = audioContext.currentTime + 0.02;\n this.previewLastTimelineUs = startTimelineUs;\n }\n\n private cancelPreviewBlockScheduling(): void {\n this.previewScheduleToken += 1;\n this.previewScheduleTask = null;\n this.previewNextBlockIndex = 0;\n this.previewNextScheduleTime = 0;\n this.previewFirstBlockOffsetUs = 0;\n this.previewLastTimelineUs = 0;\n }\n\n private realignPreviewSchedulingToTimeline(audioContext: AudioContext): void {\n const model = this.deps.preparer.getModel();\n if (!model) return;\n\n const clampedUs = Math.max(0, this.previewLastTimelineUs);\n const blockIndex = Math.floor(clampedUs / this.PREVIEW_BLOCK_DURATION_US);\n const blockStartUs = blockIndex * this.PREVIEW_BLOCK_DURATION_US;\n if (blockStartUs >= model.durationUs) return;\n\n this.stopAllPreviewSources();\n this.previewFirstBlockOffsetUs = clampedUs - blockStartUs;\n this.previewNextBlockIndex = blockIndex;\n this.previewNextScheduleTime = audioContext.currentTime + 0.02;\n }\n\n private async runPreviewBlockSchedulingLoop(\n audioContext: AudioContext,\n token: number\n ): Promise<void> {\n while (this.isPlaying && this.previewScheduleToken === token) {\n const model = this.deps.preparer.getModel();\n if (!model) return;\n\n // End-of-timeline: nothing more to schedule.\n const nextBlockStartUs = this.previewNextBlockIndex * this.PREVIEW_BLOCK_DURATION_US;\n if (nextBlockStartUs >= model.durationUs) {\n this.previewNextScheduleTime = audioContext.currentTime + this.PREVIEW_SCHEDULE_AHEAD_SEC;\n return;\n }\n\n const scheduleAheadTime = audioContext.currentTime + this.PREVIEW_SCHEDULE_AHEAD_SEC;\n if (this.previewNextScheduleTime >= scheduleAheadTime) return;\n\n const prevBlockIndex = this.previewNextBlockIndex;\n const prevScheduleTime = this.previewNextScheduleTime;\n\n await this.scheduleNextPreviewBlock(audioContext, token);\n\n // If scheduling made no progress, bail out to avoid a tight loop that can freeze UI.\n if (\n this.previewScheduleToken === token &&\n this.previewNextBlockIndex === prevBlockIndex &&\n this.previewNextScheduleTime === prevScheduleTime\n ) {\n const now = audioContext.currentTime;\n if (now - this.previewLastStallWarnAt >= 1) {\n this.previewLastStallWarnAt = now;\n console.warn('[AudioPreviewSession] scheduling stalled; stop loop to avoid spin', {\n prevBlockIndex,\n prevScheduleTime,\n now,\n });\n }\n return;\n }\n }\n }\n\n private async getOrCreateMixedBlock(blockIndex: number): Promise<AudioBuffer | null> {\n const model = this.deps.preparer.getModel();\n if (!model) return null;\n\n const token = this.previewMixToken;\n\n return await this.previewBlockCache.getOrCreate(blockIndex, async () => {\n return await this.enqueuePreviewMix(async () => {\n // If export starts while a preview mix task is already queued/running, drop it.\n if (this.deps.cacheManager.isExporting) return null;\n\n const startUs = blockIndex * this.PREVIEW_BLOCK_DURATION_US;\n const endUs = Math.min(startUs + this.PREVIEW_BLOCK_DURATION_US, model.durationUs);\n await this.deps.preparer.ensureAudioForTimeRange(startUs, endUs, {\n mode: 'blocking',\n loadResource: true,\n });\n const mixed = await this.mixer.mix(startUs, endUs);\n\n // Preview uses mixed AudioBuffer blocks as the primary cache.\n if (!this.deps.cacheManager.isExporting) this.deps.cacheManager.clearAudioCache();\n\n return mixed;\n }, token);\n });\n }\n\n private async scheduleNextPreviewBlock(audioContext: AudioContext, token: number): Promise<void> {\n const model = this.deps.preparer.getModel();\n if (!this.isPlaying || !model) return;\n if (this.previewScheduleToken !== token) return;\n\n // If we're behind the audio clock, resync to avoid attempting to schedule in the past.\n if (this.previewNextScheduleTime < audioContext.currentTime + 0.01) {\n this.realignPreviewSchedulingToTimeline(audioContext);\n }\n\n const blockIndex = this.previewNextBlockIndex;\n const blockStartUs = blockIndex * this.PREVIEW_BLOCK_DURATION_US;\n if (blockStartUs >= model.durationUs) {\n // End-of-timeline: advance state so scheduling loop can finish without stalling.\n this.previewFirstBlockOffsetUs = 0;\n this.previewNextBlockIndex = blockIndex + 1;\n this.previewNextScheduleTime = audioContext.currentTime + this.PREVIEW_SCHEDULE_AHEAD_SEC;\n return;\n }\n\n const buffer = await this.getOrCreateMixedBlock(blockIndex);\n if (!buffer) {\n // Could be cancelled (seek/reset) or an empty-range guard; advance to avoid stalling.\n this.previewFirstBlockOffsetUs = 0;\n this.previewNextBlockIndex = blockIndex + 1;\n this.previewNextScheduleTime = audioContext.currentTime + 0.02;\n return;\n }\n if (this.previewScheduleToken !== token) return;\n\n const rate = this.playbackRate || 1.0;\n const offsetUs = this.previewFirstBlockOffsetUs;\n let startTime = this.previewNextScheduleTime;\n let offsetSec = offsetUs > 0 ? offsetUs / 1_000_000 : 0;\n\n // Mixing can be slow; after await, startTime might already be in the past.\n const now = audioContext.currentTime;\n if (startTime < now + 0.01) {\n const targetStartTime = now + 0.02;\n const skippedSec = Math.max(0, (targetStartTime - startTime) * rate);\n startTime = targetStartTime;\n offsetSec += skippedSec;\n }\n\n if (offsetSec >= buffer.duration) {\n this.previewFirstBlockOffsetUs = 0;\n this.previewNextBlockIndex = blockIndex + 1;\n this.previewNextScheduleTime = startTime;\n return;\n }\n\n const remainingSec = Math.max(0, buffer.duration - offsetSec);\n if (remainingSec <= 0) return;\n\n const source = audioContext.createBufferSource();\n source.buffer = buffer;\n source.playbackRate.value = rate;\n\n const gainNode = audioContext.createGain();\n const volume = this.volume;\n\n // Fade in/out to avoid click at block boundaries.\n const fadeSec = Math.min(this.PREVIEW_BLOCK_FADE_SEC, remainingSec / 2);\n gainNode.gain.setValueAtTime(0, startTime);\n gainNode.gain.linearRampToValueAtTime(volume, startTime + fadeSec);\n gainNode.gain.setValueAtTime(volume, startTime + Math.max(fadeSec, remainingSec - fadeSec));\n gainNode.gain.linearRampToValueAtTime(0, startTime + remainingSec);\n\n source.connect(gainNode);\n gainNode.connect(audioContext.destination);\n source.start(startTime, offsetSec);\n\n const entry = { source, gain: gainNode };\n this.previewScheduledSources.add(entry);\n\n source.onended = () => {\n try {\n source.disconnect();\n gainNode.disconnect();\n } catch {\n // ignore\n }\n this.previewScheduledSources.delete(entry);\n };\n\n // Advance to next block\n this.previewFirstBlockOffsetUs = 0;\n this.previewNextBlockIndex = blockIndex + 1;\n this.previewNextScheduleTime = startTime + remainingSec / rate;\n }\n\n private stopAllPreviewSources(): void {\n for (const { source, gain } of this.previewScheduledSources) {\n try {\n source.disconnect();\n } catch {\n // ignore\n }\n try {\n source.stop(0);\n } catch {\n // ignore\n }\n try {\n gain.disconnect();\n } catch {\n // ignore\n }\n }\n this.previewScheduledSources.clear();\n }\n}\n"],"names":["blockEndUs","remainingToBoundaryUs"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AudioPreviewSession.js","sources":["../../src/orchestrator/AudioPreviewSession.ts"],"sourcesContent":["import type { TimeUs } from '../model/types';\nimport { OfflineAudioMixer } from '../stages/compose/OfflineAudioMixer';\nimport { AudioMixBlockCache } from '../cache/AudioMixBlockCache';\nimport type { CacheManager } from '../cache/CacheManager';\nimport type { RequestMode } from './types';\nimport type { AudioWindowPreparer } from './AudioWindowPreparer';\n\nexport class AudioPreviewSession {\n private mixer: OfflineAudioMixer;\n private audioContext: AudioContext | null = null;\n private volume = 1.0;\n private playbackRate = 1.0;\n private isPlaying = false;\n\n // Preview strategy (unified):\n // - Always schedule audio in fixed 60s \"mix blocks\"\n // - Cache 2~3 mixed AudioBuffer blocks (LRU) to accelerate seek\n // - Schedule ahead using AudioContext clock to avoid underrun\n private readonly PREVIEW_BLOCK_DURATION_US: TimeUs = 12 * 1_000_000; // 12s\n private readonly PREVIEW_BLOCK_CACHE_SIZE = 3;\n private readonly PREVIEW_SCHEDULE_AHEAD_SEC = 6.0; // keep enough scheduled audio to hide mixing latency\n private readonly PREVIEW_BUFFER_GUARD_US: TimeUs = 2_000_000; // if next block isn't ready near boundary -> buffering\n private readonly PREVIEW_BLOCK_FADE_SEC = 0.01; // 10ms fade-in/out to avoid click at boundaries\n\n private previewBlockCache = new AudioMixBlockCache(this.PREVIEW_BLOCK_CACHE_SIZE);\n\n private previewScheduleTask: Promise<void> | null = null;\n private previewScheduleToken = 0;\n private previewMixToken = 0;\n private previewNextBlockIndex = 0;\n private previewNextScheduleTime = 0; // AudioContext time\n private previewFirstBlockOffsetUs: TimeUs = 0; // seek offset within the first block\n private previewLastTimelineUs: TimeUs = 0;\n private previewLastStallWarnAt = 0; // AudioContext time (sec)\n\n private previewScheduledSources = new Set<{ source: AudioBufferSourceNode; gain: GainNode }>();\n private previewMixQueue: Promise<unknown> = Promise.resolve();\n\n constructor(private deps: { cacheManager: CacheManager; preparer: AudioWindowPreparer }) {\n this.mixer = new OfflineAudioMixer(deps.cacheManager, () => deps.preparer.getModel());\n }\n\n invalidatePreviewMixCache(): void {\n // Mixed AudioBuffer blocks embed per-clip audioConfig (volume/muted).\n // When model is replaced via setCompositionModel, these blocks must be invalidated,\n // otherwise preview may keep scheduling old AudioBuffers and volume changes won't take effect.\n this.previewBlockCache.clear();\n // Ensure any in-flight mix tasks are dropped.\n this.previewMixToken += 1;\n }\n\n private enqueuePreviewMix<T>(work: () => Promise<T>, token: number): Promise<T | null> {\n const run = () => work();\n const next = this.previewMixQueue.then(\n () => {\n // If a seek/reset happened since this task was enqueued, drop it.\n if (this.previewMixToken !== token) {\n return null;\n }\n return run();\n },\n () => {\n if (this.previewMixToken !== token) {\n return null;\n }\n return run();\n }\n );\n // Keep queue alive even if a task fails.\n this.previewMixQueue = next.then(\n () => undefined,\n () => undefined\n );\n return next as Promise<T | null>;\n }\n\n async ensureAudioForTime(timeUs: TimeUs, options?: { mode?: RequestMode }): Promise<void> {\n if (this.deps.cacheManager.isExporting) return;\n\n const model = this.deps.preparer.getModel();\n if (!model) return;\n\n const mode = options?.mode ?? 'blocking';\n\n // Preview contract:\n // - blocking: ensure the current 60s mixed block is ready (may be slow -> should be wrapped by buffering UI)\n // - probe: best-effort preheat current and next block without blocking\n const blockIndex = Math.floor(Math.max(0, timeUs) / this.PREVIEW_BLOCK_DURATION_US);\n\n if (mode === 'probe') {\n // Default: only preheat the current block.\n void this.getOrCreateMixedBlock(blockIndex);\n\n // If we're close enough to boundary (within scheduling lookahead), also preheat next block.\n const blockEndUs = (blockIndex + 1) * this.PREVIEW_BLOCK_DURATION_US;\n const remainingToBoundaryUs = blockEndUs - Math.max(0, timeUs);\n const lookaheadUs = Math.floor(this.PREVIEW_SCHEDULE_AHEAD_SEC * 1_000_000);\n if (remainingToBoundaryUs > 0 && remainingToBoundaryUs <= lookaheadUs) {\n void this.getOrCreateMixedBlock(blockIndex + 1);\n }\n return;\n }\n\n await this.getOrCreateMixedBlock(blockIndex);\n\n // If we're very close to the block boundary, also ensure the next block.\n const blockEndUs = (blockIndex + 1) * this.PREVIEW_BLOCK_DURATION_US;\n const remainingToBoundaryUs = blockEndUs - Math.max(0, timeUs);\n if (remainingToBoundaryUs > 0 && remainingToBoundaryUs <= this.PREVIEW_BUFFER_GUARD_US) {\n await this.getOrCreateMixedBlock(blockIndex + 1);\n }\n }\n\n isPreviewMixBlockCached(timeUs: TimeUs): boolean {\n const blockIndex = Math.floor(Math.max(0, timeUs) / this.PREVIEW_BLOCK_DURATION_US);\n return this.previewBlockCache.get(blockIndex) !== null;\n }\n\n shouldEnterBufferingForUpcomingPreviewAudio(timeUs: TimeUs): boolean {\n const model = this.deps.preparer.getModel();\n if (!model) return false;\n\n const clampedUs = Math.max(0, timeUs);\n const blockIndex = Math.floor(clampedUs / this.PREVIEW_BLOCK_DURATION_US);\n const nextBlockStartUs = (blockIndex + 1) * this.PREVIEW_BLOCK_DURATION_US;\n if (nextBlockStartUs >= model.durationUs) return false;\n\n const remainingToBoundaryUs = nextBlockStartUs - clampedUs;\n if (remainingToBoundaryUs > this.PREVIEW_BUFFER_GUARD_US) return false;\n\n // Probe next block readiness by checking cache at next block start time.\n return !this.isPreviewMixBlockCached(nextBlockStartUs);\n }\n\n async startPlayback(timeUs: TimeUs, audioContext: AudioContext): Promise<void> {\n this.audioContext = audioContext;\n\n // Resume AudioContext if suspended (required by modern browsers)\n if (audioContext.state === 'suspended') {\n await audioContext.resume();\n }\n\n // Ensure audio is decoded and ready (blocking mode for startup).\n await this.ensureAudioForTime(timeUs, { mode: 'blocking' });\n\n this.isPlaying = true;\n\n // Unified block scheduling: align to block index and schedule immediately.\n this.startPreviewBlockScheduling(timeUs, audioContext);\n // Schedule first block in blocking mode to avoid initial silence.\n await this.scheduleNextPreviewBlock(audioContext, this.previewScheduleToken);\n // Then keep scheduling in background.\n void this.scheduleAudio(timeUs, audioContext);\n }\n\n stopPlayback(): void {\n this.isPlaying = false;\n this.stopAllPreviewSources();\n this.cancelPreviewBlockScheduling();\n this.previewMixToken += 1;\n }\n\n /**\n * Schedule audio chunks ahead of playback cursor.\n */\n async scheduleAudio(currentTimelineUs: TimeUs, audioContext: AudioContext): Promise<void> {\n if (!this.isPlaying || !this.deps.preparer.getModel() || !this.audioContext) {\n return;\n }\n\n this.previewLastTimelineUs = currentTimelineUs;\n\n // Keep scheduling in the background to avoid blocking the render loop.\n if (this.previewScheduleTask) return;\n\n // Initialize if needed (e.g. after model switch without explicit startPlayback).\n if (this.previewNextScheduleTime === 0) {\n this.startPreviewBlockScheduling(currentTimelineUs, audioContext);\n }\n\n const token = this.previewScheduleToken;\n this.previewScheduleTask = this.runPreviewBlockSchedulingLoop(audioContext, token).finally(\n () => {\n if (this.previewScheduleToken === token) {\n this.previewScheduleTask = null;\n }\n }\n );\n }\n\n /**\n * Reset playback states (called on seek)\n */\n resetPlaybackStates(): void {\n this.stopAllPreviewSources();\n this.cancelPreviewBlockScheduling();\n this.previewMixToken += 1;\n }\n\n reset(): void {\n this.stopPlayback();\n this.deps.cacheManager.clearAudioCache();\n this.previewBlockCache.clear();\n this.deps.preparer.reset();\n }\n\n setVolume(volume: number): void {\n this.volume = volume;\n const audioContext = this.audioContext;\n if (!audioContext) return;\n\n // Apply volume to already scheduled nodes (small ramp to avoid click).\n const t = audioContext.currentTime;\n for (const { gain } of this.previewScheduledSources) {\n try {\n gain.gain.cancelScheduledValues(t);\n gain.gain.setValueAtTime(gain.gain.value, t);\n gain.gain.linearRampToValueAtTime(volume, t + 0.01);\n } catch {\n // ignore\n }\n }\n }\n\n setPlaybackRate(rate: number): void {\n this.playbackRate = rate;\n // Playback rate change requires restarting scheduling to keep sync with the timeline clock.\n this.resetPlaybackStates();\n }\n\n private startPreviewBlockScheduling(startTimelineUs: TimeUs, audioContext: AudioContext): void {\n this.cancelPreviewBlockScheduling();\n this.stopAllPreviewSources();\n\n const clampedUs = Math.max(0, startTimelineUs);\n const blockIndex = Math.floor(clampedUs / this.PREVIEW_BLOCK_DURATION_US);\n const blockStartUs = blockIndex * this.PREVIEW_BLOCK_DURATION_US;\n\n this.previewFirstBlockOffsetUs = clampedUs - blockStartUs;\n this.previewNextBlockIndex = blockIndex;\n this.previewNextScheduleTime = audioContext.currentTime + 0.02;\n this.previewLastTimelineUs = startTimelineUs;\n }\n\n private cancelPreviewBlockScheduling(): void {\n this.previewScheduleToken += 1;\n this.previewScheduleTask = null;\n this.previewNextBlockIndex = 0;\n this.previewNextScheduleTime = 0;\n this.previewFirstBlockOffsetUs = 0;\n this.previewLastTimelineUs = 0;\n }\n\n private realignPreviewSchedulingToTimeline(audioContext: AudioContext): void {\n const model = this.deps.preparer.getModel();\n if (!model) return;\n\n const clampedUs = Math.max(0, this.previewLastTimelineUs);\n const blockIndex = Math.floor(clampedUs / this.PREVIEW_BLOCK_DURATION_US);\n const blockStartUs = blockIndex * this.PREVIEW_BLOCK_DURATION_US;\n if (blockStartUs >= model.durationUs) return;\n\n this.stopAllPreviewSources();\n this.previewFirstBlockOffsetUs = clampedUs - blockStartUs;\n this.previewNextBlockIndex = blockIndex;\n this.previewNextScheduleTime = audioContext.currentTime + 0.02;\n }\n\n private async runPreviewBlockSchedulingLoop(\n audioContext: AudioContext,\n token: number\n ): Promise<void> {\n while (this.isPlaying && this.previewScheduleToken === token) {\n const model = this.deps.preparer.getModel();\n if (!model) return;\n\n // End-of-timeline: nothing more to schedule.\n const nextBlockStartUs = this.previewNextBlockIndex * this.PREVIEW_BLOCK_DURATION_US;\n if (nextBlockStartUs >= model.durationUs) {\n this.previewNextScheduleTime = audioContext.currentTime + this.PREVIEW_SCHEDULE_AHEAD_SEC;\n return;\n }\n\n const scheduleAheadTime = audioContext.currentTime + this.PREVIEW_SCHEDULE_AHEAD_SEC;\n if (this.previewNextScheduleTime >= scheduleAheadTime) return;\n\n const prevBlockIndex = this.previewNextBlockIndex;\n const prevScheduleTime = this.previewNextScheduleTime;\n\n await this.scheduleNextPreviewBlock(audioContext, token);\n\n // If scheduling made no progress, bail out to avoid a tight loop that can freeze UI.\n if (\n this.previewScheduleToken === token &&\n this.previewNextBlockIndex === prevBlockIndex &&\n this.previewNextScheduleTime === prevScheduleTime\n ) {\n const now = audioContext.currentTime;\n if (now - this.previewLastStallWarnAt >= 1) {\n this.previewLastStallWarnAt = now;\n console.warn('[AudioPreviewSession] scheduling stalled; stop loop to avoid spin', {\n prevBlockIndex,\n prevScheduleTime,\n now,\n });\n }\n return;\n }\n }\n }\n\n private async getOrCreateMixedBlock(blockIndex: number): Promise<AudioBuffer | null> {\n const model = this.deps.preparer.getModel();\n if (!model) return null;\n\n const token = this.previewMixToken;\n\n return await this.previewBlockCache.getOrCreate(blockIndex, async () => {\n return await this.enqueuePreviewMix(async () => {\n // If export starts while a preview mix task is already queued/running, drop it.\n if (this.deps.cacheManager.isExporting) return null;\n\n const startUs = blockIndex * this.PREVIEW_BLOCK_DURATION_US;\n const endUs = Math.min(startUs + this.PREVIEW_BLOCK_DURATION_US, model.durationUs);\n await this.deps.preparer.ensureAudioForTimeRange(startUs, endUs, {\n mode: 'blocking',\n loadResource: true,\n });\n const mixed = await this.mixer.mix(startUs, endUs);\n\n // Preview uses mixed AudioBuffer blocks as the primary cache.\n if (!this.deps.cacheManager.isExporting) this.deps.cacheManager.clearAudioCache();\n\n return mixed;\n }, token);\n });\n }\n\n private async scheduleNextPreviewBlock(audioContext: AudioContext, token: number): Promise<void> {\n const model = this.deps.preparer.getModel();\n if (!this.isPlaying || !model) return;\n if (this.previewScheduleToken !== token) return;\n\n // If we're behind the audio clock, resync to avoid attempting to schedule in the past.\n if (this.previewNextScheduleTime < audioContext.currentTime + 0.01) {\n this.realignPreviewSchedulingToTimeline(audioContext);\n }\n\n const blockIndex = this.previewNextBlockIndex;\n const blockStartUs = blockIndex * this.PREVIEW_BLOCK_DURATION_US;\n if (blockStartUs >= model.durationUs) {\n // End-of-timeline: advance state so scheduling loop can finish without stalling.\n this.previewFirstBlockOffsetUs = 0;\n this.previewNextBlockIndex = blockIndex + 1;\n this.previewNextScheduleTime = audioContext.currentTime + this.PREVIEW_SCHEDULE_AHEAD_SEC;\n return;\n }\n\n const buffer = await this.getOrCreateMixedBlock(blockIndex);\n if (!buffer) {\n // Could be cancelled (seek/reset) or an empty-range guard; advance to avoid stalling.\n this.previewFirstBlockOffsetUs = 0;\n this.previewNextBlockIndex = blockIndex + 1;\n this.previewNextScheduleTime = audioContext.currentTime + 0.02;\n return;\n }\n if (this.previewScheduleToken !== token) return;\n\n const rate = this.playbackRate || 1.0;\n const offsetUs = this.previewFirstBlockOffsetUs;\n let startTime = this.previewNextScheduleTime;\n let offsetSec = offsetUs > 0 ? offsetUs / 1_000_000 : 0;\n\n // Mixing can be slow; after await, startTime might already be in the past.\n const now = audioContext.currentTime;\n if (startTime < now + 0.01) {\n const targetStartTime = now + 0.02;\n const skippedSec = Math.max(0, (targetStartTime - startTime) * rate);\n startTime = targetStartTime;\n offsetSec += skippedSec;\n }\n\n if (offsetSec >= buffer.duration) {\n this.previewFirstBlockOffsetUs = 0;\n this.previewNextBlockIndex = blockIndex + 1;\n this.previewNextScheduleTime = startTime;\n return;\n }\n\n const remainingSec = Math.max(0, buffer.duration - offsetSec);\n if (remainingSec <= 0) return;\n\n const source = audioContext.createBufferSource();\n source.buffer = buffer;\n source.playbackRate.value = rate;\n\n const gainNode = audioContext.createGain();\n const volume = this.volume;\n\n // Fade in/out to avoid click at block boundaries.\n const fadeSec = Math.min(this.PREVIEW_BLOCK_FADE_SEC, remainingSec / 2);\n gainNode.gain.setValueAtTime(0, startTime);\n gainNode.gain.linearRampToValueAtTime(volume, startTime + fadeSec);\n gainNode.gain.setValueAtTime(volume, startTime + Math.max(fadeSec, remainingSec - fadeSec));\n gainNode.gain.linearRampToValueAtTime(0, startTime + remainingSec);\n\n source.connect(gainNode);\n gainNode.connect(audioContext.destination);\n source.start(startTime, offsetSec);\n\n const entry = { source, gain: gainNode };\n this.previewScheduledSources.add(entry);\n\n source.onended = () => {\n try {\n source.disconnect();\n gainNode.disconnect();\n } catch {\n // ignore\n }\n this.previewScheduledSources.delete(entry);\n };\n\n // Advance to next block\n this.previewFirstBlockOffsetUs = 0;\n this.previewNextBlockIndex = blockIndex + 1;\n this.previewNextScheduleTime = startTime + remainingSec / rate;\n }\n\n private stopAllPreviewSources(): void {\n for (const { source, gain } of this.previewScheduledSources) {\n try {\n source.disconnect();\n } catch {\n // ignore\n }\n try {\n source.stop(0);\n } catch {\n // ignore\n }\n try {\n gain.disconnect();\n } catch {\n // ignore\n }\n }\n this.previewScheduledSources.clear();\n }\n}\n"],"names":["blockEndUs","remainingToBoundaryUs"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioWindowPreparer.d.ts","sourceRoot":"","sources":["../../src/orchestrator/AudioWindowPreparer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAG1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAI3C,UAAU,gBAAgB;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,SAAS,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,uBAAuB;IAC/B,YAAY,EAAE,YAAY,CAAC;IAC3B,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;CACrC;AAED,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,IAAI,CAA0B;IACtC,OAAO,CAAC,KAAK,CAAiC;gBAElC,IAAI,EAAE,uBAAuB;IAIzC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAIvC,QAAQ,IAAI,gBAAgB,GAAG,IAAI;IAInC,KAAK,IAAI,IAAI;IAKb,WAAW,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAM5C;;;;;;;OAOG;IACH,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;IAmB7D,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAqDtC,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASnD;;OAEG;IACG,uBAAuB,CAC3B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;QAAE,IAAI,CAAC,EAAE,WAAW,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5E,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"AudioWindowPreparer.d.ts","sourceRoot":"","sources":["../../src/orchestrator/AudioWindowPreparer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAG1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAI3C,UAAU,gBAAgB;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,SAAS,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,uBAAuB;IAC/B,YAAY,EAAE,YAAY,CAAC;IAC3B,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;CACrC;AAED,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,IAAI,CAA0B;IACtC,OAAO,CAAC,KAAK,CAAiC;gBAElC,IAAI,EAAE,uBAAuB;IAIzC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAIvC,QAAQ,IAAI,gBAAgB,GAAG,IAAI;IAInC,KAAK,IAAI,IAAI;IAKb,WAAW,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAM5C;;;;;;;OAOG;IACH,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;IAmB7D,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAqDtC,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASnD;;OAEG;IACG,uBAAuB,CAC3B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;QAAE,IAAI,CAAC,EAAE,WAAW,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5E,OAAO,CAAC,IAAI,CAAC;IAoHhB;;OAEG;IACG,iBAAiB,CACrB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,UAAU,GAAE,OAAe,GAC1B,OAAO,CAAC,IAAI,CAAC;IAShB;;OAEG;YACW,iBAAiB;IA8E/B;;OAEG;YACW,kBAAkB;CAiEjC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MeframeEvent } from "../event/events.js";
|
|
2
2
|
import { AudioChunkDecoder } from "../stages/decode/AudioChunkDecoder.js";
|
|
3
|
-
import { hasResourceId, isAudioClip } from "../model/types.js";
|
|
3
|
+
import { hasResourceId, isAudioClip, isVideoClip, videoClipPlaybackRate } from "../model/types.js";
|
|
4
4
|
import { buildLoopedResourceSegments } from "../utils/loop-utils.js";
|
|
5
5
|
import { isTerminalMediaResourceError } from "../utils/errors.js";
|
|
6
6
|
class AudioWindowPreparer {
|
|
@@ -143,6 +143,7 @@ class AudioWindowPreparer {
|
|
|
143
143
|
const clipRelativeEndUs = Math.min(clip.durationUs, endUs - clip.startUs);
|
|
144
144
|
const trimStartUs = clip.trimStartUs ?? 0;
|
|
145
145
|
const loop = clip.trackKind === "audio" && clip.loop === true;
|
|
146
|
+
const playbackRate = loop ? 1 : isVideoClip(clip) ? videoClipPlaybackRate(clip) : 1;
|
|
146
147
|
const audioRecord = this.deps.cacheManager.audioSampleCache.get(clip.resourceId);
|
|
147
148
|
const resourceDurationUs = audioRecord?.durationUs ?? 0;
|
|
148
149
|
let segments = buildLoopedResourceSegments({
|
|
@@ -150,7 +151,8 @@ class AudioWindowPreparer {
|
|
|
150
151
|
clipRelativeEndUs,
|
|
151
152
|
trimStartUs,
|
|
152
153
|
resourceDurationUs,
|
|
153
|
-
loop
|
|
154
|
+
loop,
|
|
155
|
+
playbackRate
|
|
154
156
|
});
|
|
155
157
|
if (segments.length === 0 && clipRelativeEndUs > clipRelativeStartUs) {
|
|
156
158
|
segments = buildLoopedResourceSegments({
|
|
@@ -158,7 +160,8 @@ class AudioWindowPreparer {
|
|
|
158
160
|
clipRelativeEndUs,
|
|
159
161
|
trimStartUs,
|
|
160
162
|
resourceDurationUs,
|
|
161
|
-
loop: false
|
|
163
|
+
loop: false,
|
|
164
|
+
playbackRate
|
|
162
165
|
});
|
|
163
166
|
}
|
|
164
167
|
for (const seg of segments) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioWindowPreparer.js","sources":["../../src/orchestrator/AudioWindowPreparer.ts"],"sourcesContent":["import type { TimeUs } from '../model/types';\nimport type { CompositionModel } from '../model';\nimport type { ResourceLoader } from '../stages/load/ResourceLoader';\nimport type { EventBus } from '../event/EventBus';\nimport type { EventPayloadMap } from '../event/events';\nimport { MeframeEvent } from '../event/events';\nimport type { CacheManager } from '../cache/CacheManager';\nimport { AudioChunkDecoder } from '../stages/decode/AudioChunkDecoder';\nimport { isAudioClip, hasResourceId } from '../model/types';\nimport type { RequestMode } from './types';\nimport { buildLoopedResourceSegments } from '../utils/loop-utils';\nimport { isTerminalMediaResourceError } from '../utils/errors';\n\ninterface AudioDataMessage {\n sessionId: string;\n audioData: AudioData;\n clipStartUs: TimeUs;\n clipDurationUs: TimeUs;\n}\n\ninterface AudioWindowPreparerDeps {\n cacheManager: CacheManager;\n resourceLoader: ResourceLoader;\n eventBus: EventBus<EventPayloadMap>;\n}\n\nexport class AudioWindowPreparer {\n private activeClips = new Set<string>();\n private deps: AudioWindowPreparerDeps;\n private model: CompositionModel | null = null;\n\n constructor(deps: AudioWindowPreparerDeps) {\n this.deps = deps;\n }\n\n setModel(model: CompositionModel): void {\n this.model = model;\n }\n\n getModel(): CompositionModel | null {\n return this.model;\n }\n\n reset(): void {\n this.deps.cacheManager.clearAudioCache();\n this.activeClips.clear();\n }\n\n onAudioData(message: AudioDataMessage): void {\n const { sessionId, audioData, clipStartUs, clipDurationUs } = message;\n const globalTimeUs = clipStartUs + (audioData.timestamp ?? 0);\n this.deps.cacheManager.putClipAudioData(sessionId, audioData, clipDurationUs, globalTimeUs);\n }\n\n /**\n * Fast readiness probe for preview playback.\n *\n * This is intentionally synchronous and lightweight:\n * - Only checks resource-level readiness (download + MP4 index parsing).\n * - If any relevant resource isn't ready yet, return false.\n * - Does NOT require audio samples / PCM window coverage (probe is resource-level only).\n */\n isAudioResourceWindowReady(startUs: TimeUs, endUs: TimeUs): boolean {\n const model = this.model;\n if (!model) return true;\n\n const activeClips = model.getActiveClips(startUs, endUs);\n\n for (const clip of activeClips) {\n if (clip.trackKind !== 'audio' && clip.trackKind !== 'video') continue;\n if (!hasResourceId(clip)) continue;\n\n const resource = model.getResource(clip.resourceId);\n if (resource?.state !== 'ready') {\n return false;\n }\n }\n\n return true;\n }\n\n async activateAllAudioClips(): Promise<void> {\n const model = this.model;\n if (!model) {\n return;\n }\n\n const audioTracks = model.tracks.filter((track) => track.kind === 'audio');\n if (audioTracks.length === 0) return;\n\n // Find maximum clip count across all audio tracks\n const maxClipCount = Math.max(...audioTracks.map((track) => track.clips.length));\n\n // Horizontal loading: activate clip[0] from all tracks, then clip[1], etc.\n for (let clipIndex = 0; clipIndex < maxClipCount; clipIndex++) {\n for (const track of audioTracks) {\n const clip = track.clips[clipIndex];\n if (!clip || this.activeClips.has(clip.id)) continue;\n\n if (!isAudioClip(clip)) {\n throw new Error(`Clip ${clip.id} in audio track is not an audio clip`);\n }\n\n // Preview: Use main-thread parsing → AudioSampleCache → on-demand decode\n // Check if we have cached samples (already parsed in ResourceLoader)\n if (this.deps.cacheManager.audioSampleCache.has(clip.resourceId)) {\n // Already parsed, mark as active\n this.activeClips.add(clip.id);\n this.deps.eventBus.emit(MeframeEvent.ClipActivated, { clipId: clip.id });\n continue;\n }\n\n // Ensure resource is loaded (will be parsed and cached in main thread)\n try {\n await this.deps.resourceLoader.load(clip.resourceId, {\n isPreload: false,\n clipId: clip.id,\n trackId: track.id,\n });\n } catch (error) {\n // Preview should not be blocked by a single bad audio resource.\n if (isTerminalMediaResourceError(error)) {\n continue;\n }\n throw error;\n }\n\n // Mark as active\n this.activeClips.add(clip.id);\n this.deps.eventBus.emit(MeframeEvent.ClipActivated, { clipId: clip.id });\n }\n }\n }\n\n async deactivateClip(clipId: string): Promise<void> {\n if (!this.activeClips.has(clipId)) {\n return;\n }\n\n this.activeClips.delete(clipId);\n this.deps.cacheManager.clearClipAudioData(clipId);\n }\n\n /**\n * Core method to ensure audio for all clips in a time range\n */\n async ensureAudioForTimeRange(\n startUs: TimeUs,\n endUs: TimeUs,\n options: { mode?: RequestMode; loadResource?: boolean; strictMode?: boolean }\n ): Promise<void> {\n const model = this.model;\n if (!model) return;\n\n const { mode = 'blocking', loadResource = true, strictMode = false } = options;\n\n // Find all clips that overlap with [startUs, endUs]\n const activeClips = model.getActiveClips(startUs, endUs);\n\n const ensurePromises = activeClips.map(async (clip) => {\n // Only process audio and video clips.\n // Note: video clips may carry an embedded audio track; preview should include them as well.\n if (clip.trackKind !== 'audio' && clip.trackKind !== 'video') return;\n if (!hasResourceId(clip)) return;\n\n // Skip muted/zero-volume clips early; they should not block preview or export.\n const muted = clip.audioConfig?.muted ?? false;\n const volume = clip.audioConfig?.volume ?? 1.0;\n if (muted || volume <= 0) {\n return;\n }\n\n const resource = model.getResource(clip.resourceId);\n\n // Terminal error state: for preview (strictMode=false) skip; for export (strictMode=true) fail fast.\n if (resource?.state === 'error' && resource.error?.terminal === true) {\n if (strictMode) {\n throw new Error(`Resource ${clip.resourceId} is in error state`);\n }\n return;\n }\n\n // Skip clips without audio track (performance optimization)\n // If AudioSampleCache doesn't have this resource, and resource is ready,\n // it means the resource has no audio track (e.g., video-only or image)\n if (\n resource?.state === 'ready' &&\n !this.deps.cacheManager.audioSampleCache.has(clip.resourceId)\n ) {\n // Resource is ready but has no audio samples - skip\n return;\n }\n\n // Ensure AudioSampleCache has data\n if (!this.deps.cacheManager.audioSampleCache.has(clip.resourceId)) {\n if (!loadResource) {\n // Export mode: skip clips without cached samples\n return;\n }\n\n if (resource?.state !== 'ready') {\n // Resource not yet loaded - wait for it\n try {\n await this.deps.resourceLoader.load(clip.resourceId, {\n isPreload: false,\n clipId: clip.id,\n trackId: clip.trackId,\n });\n } catch (error) {\n if (isTerminalMediaResourceError(error)) {\n if (strictMode) {\n throw error;\n }\n return;\n }\n throw error;\n }\n }\n }\n\n // Calculate clip-relative time range\n const clipRelativeStartUs = Math.max(0, startUs - clip.startUs);\n const clipRelativeEndUs = Math.min(clip.durationUs, endUs - clip.startUs);\n\n // Convert to resource time (aligned with video architecture)\n const trimStartUs = clip.trimStartUs ?? 0;\n const loop = clip.trackKind === 'audio' && clip.loop === true;\n const audioRecord = this.deps.cacheManager.audioSampleCache.get(clip.resourceId) as {\n durationUs: TimeUs;\n } | null;\n const resourceDurationUs = audioRecord?.durationUs ?? 0;\n\n let segments = buildLoopedResourceSegments({\n clipRelativeStartUs,\n clipRelativeEndUs,\n trimStartUs,\n resourceDurationUs,\n loop,\n });\n if (segments.length === 0 && clipRelativeEndUs > clipRelativeStartUs) {\n // Fallback to non-loop mapping when resource duration isn't available yet.\n segments = buildLoopedResourceSegments({\n clipRelativeStartUs,\n clipRelativeEndUs,\n trimStartUs,\n resourceDurationUs,\n loop: false,\n });\n }\n\n // Ensure audio window using resource time coordinates\n for (const seg of segments) {\n await this.ensureAudioWindow(clip.id, seg.resourceStartUs, seg.resourceEndUs, strictMode);\n }\n });\n\n if (mode === 'probe') {\n void Promise.all(ensurePromises).catch(() => {});\n return;\n }\n await Promise.all(ensurePromises);\n }\n\n /**\n * Ensure audio window for a clip\n */\n async ensureAudioWindow(\n clipId: string,\n startUs: TimeUs,\n endUs: TimeUs,\n strictMode: boolean = false\n ): Promise<void> {\n // Check L1 cache - window-level verification\n if (this.deps.cacheManager.hasWindowPCM(clipId, startUs, endUs, strictMode)) {\n return; // Entire window has sufficient data\n }\n\n await this.decodeAudioWindow(clipId, startUs, endUs);\n }\n\n /**\n * Decode audio window for a clip\n */\n private async decodeAudioWindow(clipId: string, startUs: TimeUs, endUs: TimeUs): Promise<void> {\n const clip = this.model?.findClip(clipId);\n if (!clip || !hasResourceId(clip)) {\n return;\n }\n\n // Get audio samples from AudioSampleCache\n const audioRecord = this.deps.cacheManager.audioSampleCache.get(clip.resourceId);\n if (!audioRecord) {\n // Resource has no audio track (common for some video files)\n return;\n }\n\n // Filter chunks within window (aligned with video GOP filtering)\n const windowChunks = audioRecord.samples.filter((s) => {\n const sampleEndUs = s.timestamp + (s.duration ?? 0);\n return s.timestamp < endUs && sampleEndUs > startUs;\n });\n\n if (windowChunks.length === 0) {\n return;\n }\n\n // Incremental decoding with smart threshold strategy\n const INCREMENTAL_THRESHOLD = 0.95; // 95% coverage: skip decode\n const FULL_FALLBACK_THRESHOLD = 0.3; // <30% coverage: full decode to avoid fragmentation\n\n // Check window-level coverage\n const coverage = this.deps.cacheManager.getAudioRangeCoverage(\n clipId,\n startUs,\n endUs,\n INCREMENTAL_THRESHOLD\n );\n\n // Strategy 1: High coverage - skip decode entirely\n if (coverage.covered) {\n return;\n }\n\n // Strategy 2: Very low coverage - full decode (avoid fragmentation overhead)\n if (coverage.coverageRatio < FULL_FALLBACK_THRESHOLD) {\n await this.decodeAudioSamples(\n clipId,\n windowChunks,\n audioRecord.metadata,\n clip.durationUs,\n clip.startUs\n );\n return;\n }\n\n // Strategy 3: Medium coverage - incremental decode (30%-95%)\n const chunksToDecode = windowChunks.filter((chunk) => {\n const chunkEndUs = chunk.timestamp + (chunk.duration ?? 0);\n const chunkCoverage = this.deps.cacheManager.getAudioRangeCoverage(\n clipId,\n chunk.timestamp,\n chunkEndUs,\n 0.95\n );\n return !chunkCoverage.covered;\n });\n\n if (chunksToDecode.length === 0) {\n return;\n }\n\n // Decode only missing chunks\n await this.decodeAudioSamples(\n clipId,\n chunksToDecode,\n audioRecord.metadata,\n clip.durationUs,\n clip.startUs\n );\n }\n\n /**\n * Decode audio samples to PCM and cache\n */\n private async decodeAudioSamples(\n clipId: string,\n samples: EncodedAudioChunk[],\n config: AudioDecoderConfig,\n clipDurationUs: number,\n clipStartUs: TimeUs\n ): Promise<void> {\n // Convert description to ArrayBuffer if needed for type compatibility\n let description: ArrayBuffer | undefined;\n if (config.description) {\n if (config.description instanceof ArrayBuffer) {\n description = config.description;\n } else if (ArrayBuffer.isView(config.description)) {\n const view = config.description as Uint8Array;\n const newBuffer = new ArrayBuffer(view.byteLength);\n new Uint8Array(newBuffer).set(\n new Uint8Array(view.buffer, view.byteOffset, view.byteLength)\n );\n description = newBuffer;\n }\n }\n\n const decoderConfig = {\n codec: config.codec,\n sampleRate: config.sampleRate,\n numberOfChannels: config.numberOfChannels,\n description,\n };\n const decoder = new AudioChunkDecoder(`audio-${clipId}`, decoderConfig);\n\n try {\n // Create chunk stream\n const chunkStream = new ReadableStream<EncodedAudioChunk>({\n start(controller) {\n for (const sample of samples) {\n controller.enqueue(sample);\n }\n controller.close();\n },\n });\n\n // Decode through stream\n const audioDataStream = chunkStream.pipeThrough(decoder.createStream());\n const reader = audioDataStream.getReader();\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n if (value) {\n const globalTimeUs = clipStartUs + (value.timestamp ?? 0);\n this.deps.cacheManager.putClipAudioData(clipId, value, clipDurationUs, globalTimeUs);\n }\n }\n } finally {\n reader.releaseLock();\n }\n } catch (error) {\n console.error(`[AudioWindowPreparer] Decoder error for clip ${clipId}:`, error);\n throw error;\n } finally {\n await decoder.close();\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AudioWindowPreparer.js","sources":["../../src/orchestrator/AudioWindowPreparer.ts"],"sourcesContent":["import type { TimeUs } from '../model/types';\nimport type { CompositionModel } from '../model';\nimport type { ResourceLoader } from '../stages/load/ResourceLoader';\nimport type { EventBus } from '../event/EventBus';\nimport type { EventPayloadMap } from '../event/events';\nimport { MeframeEvent } from '../event/events';\nimport type { CacheManager } from '../cache/CacheManager';\nimport { AudioChunkDecoder } from '../stages/decode/AudioChunkDecoder';\nimport { isAudioClip, hasResourceId, isVideoClip, videoClipPlaybackRate } from '../model/types';\nimport type { RequestMode } from './types';\nimport { buildLoopedResourceSegments } from '../utils/loop-utils';\nimport { isTerminalMediaResourceError } from '../utils/errors';\n\ninterface AudioDataMessage {\n sessionId: string;\n audioData: AudioData;\n clipStartUs: TimeUs;\n clipDurationUs: TimeUs;\n}\n\ninterface AudioWindowPreparerDeps {\n cacheManager: CacheManager;\n resourceLoader: ResourceLoader;\n eventBus: EventBus<EventPayloadMap>;\n}\n\nexport class AudioWindowPreparer {\n private activeClips = new Set<string>();\n private deps: AudioWindowPreparerDeps;\n private model: CompositionModel | null = null;\n\n constructor(deps: AudioWindowPreparerDeps) {\n this.deps = deps;\n }\n\n setModel(model: CompositionModel): void {\n this.model = model;\n }\n\n getModel(): CompositionModel | null {\n return this.model;\n }\n\n reset(): void {\n this.deps.cacheManager.clearAudioCache();\n this.activeClips.clear();\n }\n\n onAudioData(message: AudioDataMessage): void {\n const { sessionId, audioData, clipStartUs, clipDurationUs } = message;\n const globalTimeUs = clipStartUs + (audioData.timestamp ?? 0);\n this.deps.cacheManager.putClipAudioData(sessionId, audioData, clipDurationUs, globalTimeUs);\n }\n\n /**\n * Fast readiness probe for preview playback.\n *\n * This is intentionally synchronous and lightweight:\n * - Only checks resource-level readiness (download + MP4 index parsing).\n * - If any relevant resource isn't ready yet, return false.\n * - Does NOT require audio samples / PCM window coverage (probe is resource-level only).\n */\n isAudioResourceWindowReady(startUs: TimeUs, endUs: TimeUs): boolean {\n const model = this.model;\n if (!model) return true;\n\n const activeClips = model.getActiveClips(startUs, endUs);\n\n for (const clip of activeClips) {\n if (clip.trackKind !== 'audio' && clip.trackKind !== 'video') continue;\n if (!hasResourceId(clip)) continue;\n\n const resource = model.getResource(clip.resourceId);\n if (resource?.state !== 'ready') {\n return false;\n }\n }\n\n return true;\n }\n\n async activateAllAudioClips(): Promise<void> {\n const model = this.model;\n if (!model) {\n return;\n }\n\n const audioTracks = model.tracks.filter((track) => track.kind === 'audio');\n if (audioTracks.length === 0) return;\n\n // Find maximum clip count across all audio tracks\n const maxClipCount = Math.max(...audioTracks.map((track) => track.clips.length));\n\n // Horizontal loading: activate clip[0] from all tracks, then clip[1], etc.\n for (let clipIndex = 0; clipIndex < maxClipCount; clipIndex++) {\n for (const track of audioTracks) {\n const clip = track.clips[clipIndex];\n if (!clip || this.activeClips.has(clip.id)) continue;\n\n if (!isAudioClip(clip)) {\n throw new Error(`Clip ${clip.id} in audio track is not an audio clip`);\n }\n\n // Preview: Use main-thread parsing → AudioSampleCache → on-demand decode\n // Check if we have cached samples (already parsed in ResourceLoader)\n if (this.deps.cacheManager.audioSampleCache.has(clip.resourceId)) {\n // Already parsed, mark as active\n this.activeClips.add(clip.id);\n this.deps.eventBus.emit(MeframeEvent.ClipActivated, { clipId: clip.id });\n continue;\n }\n\n // Ensure resource is loaded (will be parsed and cached in main thread)\n try {\n await this.deps.resourceLoader.load(clip.resourceId, {\n isPreload: false,\n clipId: clip.id,\n trackId: track.id,\n });\n } catch (error) {\n // Preview should not be blocked by a single bad audio resource.\n if (isTerminalMediaResourceError(error)) {\n continue;\n }\n throw error;\n }\n\n // Mark as active\n this.activeClips.add(clip.id);\n this.deps.eventBus.emit(MeframeEvent.ClipActivated, { clipId: clip.id });\n }\n }\n }\n\n async deactivateClip(clipId: string): Promise<void> {\n if (!this.activeClips.has(clipId)) {\n return;\n }\n\n this.activeClips.delete(clipId);\n this.deps.cacheManager.clearClipAudioData(clipId);\n }\n\n /**\n * Core method to ensure audio for all clips in a time range\n */\n async ensureAudioForTimeRange(\n startUs: TimeUs,\n endUs: TimeUs,\n options: { mode?: RequestMode; loadResource?: boolean; strictMode?: boolean }\n ): Promise<void> {\n const model = this.model;\n if (!model) return;\n\n const { mode = 'blocking', loadResource = true, strictMode = false } = options;\n\n // Find all clips that overlap with [startUs, endUs]\n const activeClips = model.getActiveClips(startUs, endUs);\n\n const ensurePromises = activeClips.map(async (clip) => {\n // Only process audio and video clips.\n // Note: video clips may carry an embedded audio track; preview should include them as well.\n if (clip.trackKind !== 'audio' && clip.trackKind !== 'video') return;\n if (!hasResourceId(clip)) return;\n\n // Skip muted/zero-volume clips early; they should not block preview or export.\n const muted = clip.audioConfig?.muted ?? false;\n const volume = clip.audioConfig?.volume ?? 1.0;\n if (muted || volume <= 0) {\n return;\n }\n\n const resource = model.getResource(clip.resourceId);\n\n // Terminal error state: for preview (strictMode=false) skip; for export (strictMode=true) fail fast.\n if (resource?.state === 'error' && resource.error?.terminal === true) {\n if (strictMode) {\n throw new Error(`Resource ${clip.resourceId} is in error state`);\n }\n return;\n }\n\n // Skip clips without audio track (performance optimization)\n // If AudioSampleCache doesn't have this resource, and resource is ready,\n // it means the resource has no audio track (e.g., video-only or image)\n if (\n resource?.state === 'ready' &&\n !this.deps.cacheManager.audioSampleCache.has(clip.resourceId)\n ) {\n // Resource is ready but has no audio samples - skip\n return;\n }\n\n // Ensure AudioSampleCache has data\n if (!this.deps.cacheManager.audioSampleCache.has(clip.resourceId)) {\n if (!loadResource) {\n // Export mode: skip clips without cached samples\n return;\n }\n\n if (resource?.state !== 'ready') {\n // Resource not yet loaded - wait for it\n try {\n await this.deps.resourceLoader.load(clip.resourceId, {\n isPreload: false,\n clipId: clip.id,\n trackId: clip.trackId,\n });\n } catch (error) {\n if (isTerminalMediaResourceError(error)) {\n if (strictMode) {\n throw error;\n }\n return;\n }\n throw error;\n }\n }\n }\n\n // Calculate clip-relative time range\n const clipRelativeStartUs = Math.max(0, startUs - clip.startUs);\n const clipRelativeEndUs = Math.min(clip.durationUs, endUs - clip.startUs);\n\n // Convert to resource time (aligned with video architecture)\n const trimStartUs = clip.trimStartUs ?? 0;\n const loop = clip.trackKind === 'audio' && clip.loop === true;\n const playbackRate = loop ? 1 : isVideoClip(clip) ? videoClipPlaybackRate(clip) : 1;\n const audioRecord = this.deps.cacheManager.audioSampleCache.get(clip.resourceId) as {\n durationUs: TimeUs;\n } | null;\n const resourceDurationUs = audioRecord?.durationUs ?? 0;\n\n let segments = buildLoopedResourceSegments({\n clipRelativeStartUs,\n clipRelativeEndUs,\n trimStartUs,\n resourceDurationUs,\n loop,\n playbackRate,\n });\n if (segments.length === 0 && clipRelativeEndUs > clipRelativeStartUs) {\n // Fallback to non-loop mapping when resource duration isn't available yet.\n segments = buildLoopedResourceSegments({\n clipRelativeStartUs,\n clipRelativeEndUs,\n trimStartUs,\n resourceDurationUs,\n loop: false,\n playbackRate,\n });\n }\n\n // Ensure audio window using resource time coordinates\n for (const seg of segments) {\n await this.ensureAudioWindow(clip.id, seg.resourceStartUs, seg.resourceEndUs, strictMode);\n }\n });\n\n if (mode === 'probe') {\n void Promise.all(ensurePromises).catch(() => {});\n return;\n }\n await Promise.all(ensurePromises);\n }\n\n /**\n * Ensure audio window for a clip\n */\n async ensureAudioWindow(\n clipId: string,\n startUs: TimeUs,\n endUs: TimeUs,\n strictMode: boolean = false\n ): Promise<void> {\n // Check L1 cache - window-level verification\n if (this.deps.cacheManager.hasWindowPCM(clipId, startUs, endUs, strictMode)) {\n return; // Entire window has sufficient data\n }\n\n await this.decodeAudioWindow(clipId, startUs, endUs);\n }\n\n /**\n * Decode audio window for a clip\n */\n private async decodeAudioWindow(clipId: string, startUs: TimeUs, endUs: TimeUs): Promise<void> {\n const clip = this.model?.findClip(clipId);\n if (!clip || !hasResourceId(clip)) {\n return;\n }\n\n // Get audio samples from AudioSampleCache\n const audioRecord = this.deps.cacheManager.audioSampleCache.get(clip.resourceId);\n if (!audioRecord) {\n // Resource has no audio track (common for some video files)\n return;\n }\n\n // Filter chunks within window (aligned with video GOP filtering)\n const windowChunks = audioRecord.samples.filter((s) => {\n const sampleEndUs = s.timestamp + (s.duration ?? 0);\n return s.timestamp < endUs && sampleEndUs > startUs;\n });\n\n if (windowChunks.length === 0) {\n return;\n }\n\n // Incremental decoding with smart threshold strategy\n const INCREMENTAL_THRESHOLD = 0.95; // 95% coverage: skip decode\n const FULL_FALLBACK_THRESHOLD = 0.3; // <30% coverage: full decode to avoid fragmentation\n\n // Check window-level coverage\n const coverage = this.deps.cacheManager.getAudioRangeCoverage(\n clipId,\n startUs,\n endUs,\n INCREMENTAL_THRESHOLD\n );\n\n // Strategy 1: High coverage - skip decode entirely\n if (coverage.covered) {\n return;\n }\n\n // Strategy 2: Very low coverage - full decode (avoid fragmentation overhead)\n if (coverage.coverageRatio < FULL_FALLBACK_THRESHOLD) {\n await this.decodeAudioSamples(\n clipId,\n windowChunks,\n audioRecord.metadata,\n clip.durationUs,\n clip.startUs\n );\n return;\n }\n\n // Strategy 3: Medium coverage - incremental decode (30%-95%)\n const chunksToDecode = windowChunks.filter((chunk) => {\n const chunkEndUs = chunk.timestamp + (chunk.duration ?? 0);\n const chunkCoverage = this.deps.cacheManager.getAudioRangeCoverage(\n clipId,\n chunk.timestamp,\n chunkEndUs,\n 0.95\n );\n return !chunkCoverage.covered;\n });\n\n if (chunksToDecode.length === 0) {\n return;\n }\n\n // Decode only missing chunks\n await this.decodeAudioSamples(\n clipId,\n chunksToDecode,\n audioRecord.metadata,\n clip.durationUs,\n clip.startUs\n );\n }\n\n /**\n * Decode audio samples to PCM and cache\n */\n private async decodeAudioSamples(\n clipId: string,\n samples: EncodedAudioChunk[],\n config: AudioDecoderConfig,\n clipDurationUs: number,\n clipStartUs: TimeUs\n ): Promise<void> {\n // Convert description to ArrayBuffer if needed for type compatibility\n let description: ArrayBuffer | undefined;\n if (config.description) {\n if (config.description instanceof ArrayBuffer) {\n description = config.description;\n } else if (ArrayBuffer.isView(config.description)) {\n const view = config.description as Uint8Array;\n const newBuffer = new ArrayBuffer(view.byteLength);\n new Uint8Array(newBuffer).set(\n new Uint8Array(view.buffer, view.byteOffset, view.byteLength)\n );\n description = newBuffer;\n }\n }\n\n const decoderConfig = {\n codec: config.codec,\n sampleRate: config.sampleRate,\n numberOfChannels: config.numberOfChannels,\n description,\n };\n const decoder = new AudioChunkDecoder(`audio-${clipId}`, decoderConfig);\n\n try {\n // Create chunk stream\n const chunkStream = new ReadableStream<EncodedAudioChunk>({\n start(controller) {\n for (const sample of samples) {\n controller.enqueue(sample);\n }\n controller.close();\n },\n });\n\n // Decode through stream\n const audioDataStream = chunkStream.pipeThrough(decoder.createStream());\n const reader = audioDataStream.getReader();\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n if (value) {\n const globalTimeUs = clipStartUs + (value.timestamp ?? 0);\n this.deps.cacheManager.putClipAudioData(clipId, value, clipDurationUs, globalTimeUs);\n }\n }\n } finally {\n reader.releaseLock();\n }\n } catch (error) {\n console.error(`[AudioWindowPreparer] Decoder error for clip ${clipId}:`, error);\n throw error;\n } finally {\n await decoder.close();\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CompositionPlanner.d.ts","sourceRoot":"","sources":["../../src/orchestrator/CompositionPlanner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAChB,IAAI,EAKL,MAAM,UAAU,CAAC;AAGlB,OAAO,KAAK,EACV,kBAAkB,EAQnB,MAAM,gCAAgC,CAAC;AAIxC,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEjD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,cAAc,CAAC;IACrB,YAAY,CAAC,EAAE,kBAAkB,CAAC;CACnC;AAcD,UAAU,oBAAoB;IAC5B,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACrB,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACpB;AAED,UAAU,QAAQ;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,kBAAkB,CAAC;IACjC,SAAS,EAAE,oBAAoB,CAAC;CACjC;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,KAAK,CAAiC;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+B;IAEzD,QAAQ,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAKvC,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI;IAwB1D,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIjC,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI;IAsBpD;;;OAGG;IACH,UAAU,CAAC,MAAM,EAAE,gBAAgB,EAAE,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,gBAAgB,EAAE;IAoEtF,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,QAAQ;IAqBlE,OAAO,CAAC,oBAAoB;IAwB5B,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,eAAe;IAyBvB,OAAO,CAAC,oBAAoB;
|
|
1
|
+
{"version":3,"file":"CompositionPlanner.d.ts","sourceRoot":"","sources":["../../src/orchestrator/CompositionPlanner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAChB,IAAI,EAKL,MAAM,UAAU,CAAC;AAGlB,OAAO,KAAK,EACV,kBAAkB,EAQnB,MAAM,gCAAgC,CAAC;AAIxC,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEjD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,cAAc,CAAC;IACrB,YAAY,CAAC,EAAE,kBAAkB,CAAC;CACnC;AAcD,UAAU,oBAAoB;IAC5B,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACrB,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACpB;AAED,UAAU,QAAQ;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,kBAAkB,CAAC;IACjC,SAAS,EAAE,oBAAoB,CAAC;CACjC;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,KAAK,CAAiC;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+B;IAEzD,QAAQ,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAKvC,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI;IAwB1D,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIjC,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI;IAsBpD;;;OAGG;IACH,UAAU,CAAC,MAAM,EAAE,gBAAgB,EAAE,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,gBAAgB,EAAE;IAoEtF,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,QAAQ;IAqBlE,OAAO,CAAC,oBAAoB;IAwB5B,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,eAAe;IAyBvB,OAAO,CAAC,oBAAoB;IAoC5B,OAAO,CAAC,qBAAqB;IAiC7B,OAAO,CAAC,0BAA0B;IAiBlC,OAAO,CAAC,sBAAsB;IA4F9B,OAAO,CAAC,qBAAqB;IAe7B,OAAO,CAAC,sBAAsB;IAU9B,OAAO,CAAC,oBAAoB;IA8B5B,OAAO,CAAC,gBAAgB;IAqBxB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,gBAAgB;CAQzB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isVideoClip } from "../model/types.js";
|
|
1
|
+
import { isVideoClip, videoClipPlaybackRate } from "../model/types.js";
|
|
2
2
|
import { getFontConfig } from "../stages/compose/font-system/FontManager.js";
|
|
3
3
|
import "../stages/compose/font-system/font-templates.js";
|
|
4
4
|
import { filterRenderConfig } from "../utils/object-utils.js";
|
|
@@ -198,10 +198,12 @@ class CompositionPlanner {
|
|
|
198
198
|
}
|
|
199
199
|
const status = "ready";
|
|
200
200
|
this.registerResourceUsage(clip.resourceId, status, resources);
|
|
201
|
+
const rate = videoClipPlaybackRate(clip);
|
|
201
202
|
const payload = {
|
|
202
203
|
resourceId: clip.resourceId,
|
|
203
204
|
trimStartUs: clip.trimStartUs ?? 0,
|
|
204
|
-
durationUs: clip.durationUs
|
|
205
|
+
durationUs: clip.durationUs,
|
|
206
|
+
playbackRate: rate
|
|
205
207
|
};
|
|
206
208
|
const filteredRenderConfig = filterRenderConfig(clip.renderConfig, `video clip ${clip.id}`);
|
|
207
209
|
if (filteredRenderConfig) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CompositionPlanner.js","sources":["../../src/orchestrator/CompositionPlanner.ts"],"sourcesContent":["import type {\n CompositionModel,\n CompositionPatch,\n Clip,\n Attachment,\n Transition,\n TimeUs,\n CaptionAttachmentData,\n} from '../model';\nimport { isVideoClip } from '../model/types';\nimport type { VideoComposeConfig } from '../stages/compose/types';\nimport type {\n ClipInstructionSet,\n SerializedLayerPlan,\n SerializedTransitionPlan,\n SerializedTextLayerPayload,\n SerializedImageLayerPayload,\n SerializedMaskLayerPayload,\n SerializedEffectLayerPayload,\n ClipInstructionStatus,\n} from '../stages/compose/instructions';\nimport { getFontConfig, type LocaleCode } from '../stages/compose/font-system';\nimport { filterRenderConfig } from '../utils/object-utils';\n\nexport type ClipUpdateType = 'update' | 'remove';\n\nexport interface ClipUpdateResult {\n clipId: string;\n trackId: string;\n revision: number;\n type: ClipUpdateType;\n instructions?: ClipInstructionSet;\n}\n\nconst DEFAULT_COMPOSITION_WIDTH = 1280;\nconst DEFAULT_COMPOSITION_HEIGHT = 720;\nconst DEFAULT_COMPOSITION_FPS = 30;\n\nconst ATTACHMENT_TYPE_MAP: Record<string, SerializedLayerPlan['type']> = {\n caption: 'text',\n overlay: 'image',\n mask: 'mask',\n};\n\nconst IMAGE_RESOURCE_TYPES = new Set(['image', 'sticker', 'mask']);\n\ninterface ClipPlanResourceRefs {\n pending: Set<string>;\n ready: Set<string>;\n}\n\ninterface ClipPlan {\n clipId: string;\n trackId: string;\n revision: number;\n instructions: ClipInstructionSet;\n resources: ClipPlanResourceRefs;\n}\n\nexport class CompositionPlanner {\n private model: CompositionModel | null = null;\n private readonly clipPlans = new Map<string, ClipPlan>();\n\n setModel(model: CompositionModel): void {\n this.model = model;\n this.clipPlans.clear();\n }\n\n getInstructions(clipId: string): ClipInstructionSet | null {\n const plan = this.clipPlans.get(clipId);\n if (plan) {\n const clip = this.model?.findClip(clipId);\n if (!clip) {\n return plan.instructions;\n }\n if (this.needsPlanRefresh(clip, plan)) {\n const refreshed = this.buildClipPlan(clip, { cache: true });\n return refreshed.instructions;\n }\n return plan.instructions;\n }\n if (!this.model) {\n return null;\n }\n const clip = this.model.findClip(clipId);\n if (!clip) {\n return null;\n }\n const newPlan = this.buildClipPlan(clip, { cache: true });\n return newPlan.instructions;\n }\n\n releaseClip(clipId: string): void {\n this.clipPlans.delete(clipId);\n }\n\n refreshClip(clipId: string): ClipUpdateResult | null {\n if (!this.model) {\n return null;\n }\n\n const clip = this.model.findClip(clipId);\n if (!clip) {\n return null;\n }\n\n const plan = this.buildClipPlan(clip, { cache: true });\n this.clipPlans.set(clipId, plan);\n\n return {\n clipId,\n trackId: clip.trackId as string,\n revision: plan.revision,\n type: 'update',\n instructions: plan.instructions,\n };\n }\n\n /**\n * Apply patch and rebuild instructions for affected clips\n * Simplified for 2-Clip strategy - any change requires pipeline restart\n */\n applyPatch(_patch: CompositionPatch, affectedClipIds: Set<string>): ClipUpdateResult[] {\n if (!this.model) {\n return [];\n }\n const results: ClipUpdateResult[] = [];\n\n // Rebuild instructions for affected clips\n for (const clipId of affectedClipIds) {\n const clip = this.model.findClip(clipId);\n if (!clip) {\n // Clip was removed\n const plan = this.clipPlans.get(clipId);\n this.clipPlans.delete(clipId);\n if (plan) {\n results.push({\n clipId,\n trackId: plan.trackId,\n revision: plan.revision + 1,\n type: 'remove',\n instructions: undefined,\n });\n }\n continue;\n }\n\n // Only video clips need visual composition plans\n // Use both trackKind and track lookup for robustness\n const isVideoClip =\n clip.trackKind === 'video' ||\n (clip.trackId && this.model.findTrack(clip.trackId)?.kind === 'video');\n\n if (!isVideoClip) {\n continue;\n }\n\n // Rebuild plan for existing video clip (any change = pipeline restart)\n const plan = this.buildClipPlan(clip, { cache: false });\n this.clipPlans.set(clip.id, plan);\n\n results.push({\n clipId: clip.id,\n trackId: clip.trackId as string,\n revision: plan.revision,\n type: 'update',\n instructions: plan.instructions,\n });\n }\n\n // Check for orphaned clip plans (clips removed but not in affectedClipIds)\n for (const clipId of this.clipPlans.keys()) {\n if (!this.model.findClip(clipId) && !affectedClipIds.has(clipId)) {\n const plan = this.clipPlans.get(clipId);\n this.clipPlans.delete(clipId);\n if (plan) {\n results.push({\n clipId,\n trackId: plan.trackId,\n revision: plan.revision + 1,\n type: 'remove',\n instructions: undefined,\n });\n }\n }\n }\n\n return results;\n }\n\n buildClipPlan(clip: Clip, options?: { cache?: boolean }): ClipPlan {\n if (!this.model) {\n throw new Error('No composition model set');\n }\n const cache = options?.cache ?? true;\n const previous = this.clipPlans.get(clip.id);\n const revision = (previous?.revision ?? 0) + 1;\n const instructionContext = this.createInstructionSet(clip, revision);\n const plan: ClipPlan = {\n clipId: clip.id,\n trackId: clip.trackId as string,\n revision,\n instructions: instructionContext.instructions,\n resources: instructionContext.resources,\n };\n if (cache) {\n this.clipPlans.set(clip.id, plan);\n }\n return plan;\n }\n\n private createInstructionSet(\n clip: Clip,\n revision: number\n ): { instructions: ClipInstructionSet; resources: ClipPlanResourceRefs } {\n if (!this.model) {\n throw new Error('No composition model set');\n }\n const baseConfig = this.buildBaseConfig(clip);\n const layerResult = this.buildLayerPlans(clip);\n const transitions = this.buildTransitionPlans(clip);\n return {\n instructions: {\n clipId: clip.id,\n trackId: clip.trackId as string,\n revision,\n baseConfig,\n layers: layerResult.layers,\n transitions,\n status: layerResult.status,\n },\n resources: layerResult.resources,\n };\n }\n\n private buildBaseConfig(clip: Clip): VideoComposeConfig {\n const renderConfig = this.model?.renderConfig;\n return {\n width: renderConfig?.width ?? DEFAULT_COMPOSITION_WIDTH,\n height: renderConfig?.height ?? DEFAULT_COMPOSITION_HEIGHT,\n fps: this.model?.fps ?? DEFAULT_COMPOSITION_FPS,\n backgroundColor: renderConfig?.backgroundColor ?? '#000000',\n timeline: {\n clipId: clip.id,\n trackId: clip.trackId ?? 'main',\n clipStartUs: clip.startUs,\n clipDurationUs: clip.durationUs,\n compositionFps: this.model?.fps ?? DEFAULT_COMPOSITION_FPS,\n },\n };\n }\n\n private buildLayerPlans(clip: Clip): {\n layers: SerializedLayerPlan[];\n status: ClipInstructionStatus;\n resources: ClipPlanResourceRefs;\n } {\n const layers: SerializedLayerPlan[] = [];\n const resources: ClipPlanResourceRefs = {\n pending: new Set<string>(),\n ready: new Set<string>(),\n };\n const baseLayer = this.createBaseVideoLayer(clip, resources);\n layers.push(baseLayer);\n const attachments = clip.attachments ?? [];\n\n for (let index = 0; index < attachments.length; index += 1) {\n const attachment = attachments[index];\n if (attachment) {\n const layer = this.attachmentToLayerPlan(clip, attachment, index + 1, resources);\n layers.push(layer);\n }\n }\n // Always ready (resources are preloaded before export)\n return { layers, status: 'ready', resources };\n }\n\n private createBaseVideoLayer(clip: Clip, resources: ClipPlanResourceRefs): SerializedLayerPlan {\n if (!isVideoClip(clip)) {\n throw new Error(`Clip ${clip.id} is not a video clip`);\n }\n const status: ClipInstructionStatus = 'ready';\n this.registerResourceUsage(clip.resourceId, status, resources);\n\n const payload: any = {\n resourceId: clip.resourceId,\n trimStartUs: clip.trimStartUs ?? 0,\n durationUs: clip.durationUs,\n };\n\n // Add renderConfig if valid\n const filteredRenderConfig = filterRenderConfig(clip.renderConfig, `video clip ${clip.id}`);\n if (filteredRenderConfig) {\n payload.renderConfig = filteredRenderConfig;\n }\n\n return {\n layerId: `${clip.id}-base-video`,\n type: 'video',\n activeRanges: [\n {\n startUs: 0,\n endUs: clip.durationUs,\n },\n ],\n payload,\n status,\n zIndex: 0,\n };\n }\n\n private attachmentToLayerPlan(\n clip: Clip,\n attachment: Attachment,\n zIndex: number,\n resources: ClipPlanResourceRefs\n ): SerializedLayerPlan {\n const clipDuration = clip.durationUs;\n const startUs = Math.max(0, attachment.startUs);\n const endUs = Math.min(clipDuration, startUs + attachment.durationUs);\n const type = this.resolveAttachmentLayerType(attachment);\n const payload = this.buildAttachmentPayload(attachment, type);\n // Always mark as ready for export (resources are preloaded)\n const status: ClipInstructionStatus = 'ready';\n const resourceId = (payload as any).resourceId;\n if (resourceId && typeof resourceId === 'string') {\n this.registerResourceUsage(resourceId, status, resources);\n }\n\n return {\n layerId: `${clip.id}-attachment-${attachment.id}`,\n type,\n activeRanges: [\n {\n startUs,\n endUs,\n },\n ],\n payload,\n status,\n zIndex,\n } as SerializedLayerPlan;\n }\n\n private resolveAttachmentLayerType(attachment: Attachment): SerializedLayerPlan['type'] {\n const mappedType = ATTACHMENT_TYPE_MAP[attachment.kind];\n if (mappedType) {\n return mappedType;\n }\n if (typeof attachment.data.text === 'string') {\n return 'text';\n }\n if (attachment.data.resourceId) {\n const resource = this.model?.getResource(attachment.data.resourceId as string);\n if (resource && IMAGE_RESOURCE_TYPES.has(resource.type)) {\n return 'image';\n }\n }\n return 'effect';\n }\n\n private buildAttachmentPayload(\n attachment: Attachment,\n type: SerializedLayerPlan['type']\n ): SerializedLayerPlan['payload'] {\n const basePayload: Record<string, unknown> = {\n ...attachment.data,\n attachmentId: attachment.id,\n };\n if (type === 'text') {\n const text = this.getStringField(attachment.data, 'text') || '';\n let localeCode = this.getStringField(attachment.data, 'localeCode') as LocaleCode | undefined;\n let fontTemplate = this.getStringField(attachment.data, 'fontTemplate');\n const fontFamily = this.getStringField(attachment.data, 'fontFamily');\n const animation = attachment.data.animation as CaptionAttachmentData['animation'] | undefined;\n const letterCase = this.getStringField(attachment.data, 'letterCase') as\n | 'upper'\n | 'lower'\n | 'none'\n | undefined;\n const wordTimings = attachment.data.wordTimings as\n | CaptionAttachmentData['wordTimings']\n | undefined;\n\n if (!localeCode) {\n localeCode = 'en-US';\n }\n\n if (!fontTemplate) {\n fontTemplate = this.getDefaultFontTemplate(localeCode);\n }\n\n const fontConfig = getFontConfig(localeCode, fontTemplate, fontFamily);\n\n const payload: SerializedTextLayerPayload = {\n text,\n localeCode,\n fontConfig,\n letterCase,\n wordTimings,\n ...basePayload,\n };\n\n if (animation) {\n payload.animation = {\n ...animation,\n type: animation.type,\n glowColor: animation.glowColor as string | undefined,\n glowIntensity: animation.glowIntensity as number | undefined,\n transitionFrames: animation.transitionFrames as number | undefined,\n highlightColor: animation.highlightColor as string | undefined,\n };\n }\n\n return payload;\n }\n if (type === 'image') {\n const imagePayload: SerializedImageLayerPayload = {\n ...basePayload,\n resourceId: this.getStringField(attachment.data, 'resourceId') || '',\n } as SerializedImageLayerPayload;\n\n // Add renderConfig if valid\n const filteredRenderConfig = filterRenderConfig(\n attachment.data.renderConfig as any,\n `image attachment ${attachment.id}`\n );\n if (filteredRenderConfig) {\n imagePayload.renderConfig = filteredRenderConfig;\n }\n\n // Add animation config for overlay attachments\n if (attachment.kind === 'overlay' && attachment.data.animation) {\n imagePayload.animation = {\n ...attachment.data.animation,\n overlayClipStartUs: attachment.data.overlayClipStartUs,\n mainClipStartUs: attachment.data.mainClipStartUs,\n } as any;\n }\n\n return imagePayload;\n }\n if (type === 'mask') {\n return {\n ...basePayload,\n resourceId: this.getStringField(attachment.data, 'resourceId'),\n } as SerializedMaskLayerPayload;\n }\n return {\n ...basePayload,\n } as SerializedEffectLayerPayload;\n }\n\n private registerResourceUsage(\n identifier: string,\n status: ClipInstructionStatus,\n resources: ClipPlanResourceRefs\n ): void {\n if (!identifier) {\n return;\n }\n if (status === 'ready') {\n resources.ready.add(identifier);\n } else {\n resources.pending.add(identifier);\n }\n }\n\n private getDefaultFontTemplate(_locale: LocaleCode): string {\n // Select template based on canvas aspect ratio\n const width = this.model?.renderConfig?.width || DEFAULT_COMPOSITION_WIDTH;\n const height = this.model?.renderConfig?.height || DEFAULT_COMPOSITION_HEIGHT;\n\n // Landscape (16:9) vs Portrait (9:16)\n const isLandscape = width > height;\n return isLandscape ? 'baseSubtitle16_9' : 'baseSubtitle';\n }\n\n private buildTransitionPlans(clip: Clip): SerializedTransitionPlan[] {\n const transitions: SerializedTransitionPlan[] = [];\n const track = clip.trackId ? this.model?.findTrack(clip.trackId) : null;\n if (clip.transitionIn) {\n transitions.push(this.transitionToPlan(clip.transitionIn, 0, clip.durationUs));\n }\n if (clip.transitionOut) {\n const startUs = Math.max(0, clip.durationUs - clip.transitionOut.durationUs);\n transitions.push(this.transitionToPlan(clip.transitionOut, startUs, clip.durationUs));\n }\n if (track?.effects?.length) {\n for (const effect of track.effects) {\n transitions.push({\n transitionId: effect.id,\n range: {\n startUs: 0,\n endUs: clip.durationUs,\n },\n params: {\n type: effect.effectType,\n easing: effect.params?.easing as string | undefined,\n durationUs: effect.params?.durationUs as TimeUs | undefined,\n payload: effect.params,\n },\n });\n }\n }\n return transitions;\n }\n\n private transitionToPlan(\n transition: Transition,\n startUs: TimeUs,\n clipDurationUs: TimeUs\n ): SerializedTransitionPlan {\n const duration = Math.min(transition.durationUs, clipDurationUs);\n const clampedStart = Math.max(0, Math.min(startUs, clipDurationUs));\n const clampedEnd = Math.min(clampedStart + duration, clipDurationUs);\n return {\n transitionId: transition.id,\n range: {\n startUs: clampedStart,\n endUs: clampedEnd,\n },\n params: {\n type: transition.transitionType,\n ...transition.params,\n },\n };\n }\n\n private getStringField(data: Record<string, unknown>, key: string): string | undefined {\n const value = data[key];\n return typeof value === 'string' ? value : undefined;\n }\n\n private needsPlanRefresh(clip: Clip, plan: ClipPlan): boolean {\n // Check if attachments count changed\n const currentAttachmentCount = (clip.attachments ?? []).length;\n const cachedAttachmentCount = plan.instructions.layers.filter(\n (layer) => layer.payload.attachmentId\n ).length;\n return currentAttachmentCount !== cachedAttachmentCount;\n }\n}\n"],"names":["clip","plan","isVideoClip"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CompositionPlanner.js","sources":["../../src/orchestrator/CompositionPlanner.ts"],"sourcesContent":["import type {\n CompositionModel,\n CompositionPatch,\n Clip,\n Attachment,\n Transition,\n TimeUs,\n CaptionAttachmentData,\n} from '../model';\nimport { isVideoClip, videoClipPlaybackRate } from '../model/types';\nimport type { VideoComposeConfig } from '../stages/compose/types';\nimport type {\n ClipInstructionSet,\n SerializedLayerPlan,\n SerializedTransitionPlan,\n SerializedTextLayerPayload,\n SerializedImageLayerPayload,\n SerializedMaskLayerPayload,\n SerializedEffectLayerPayload,\n ClipInstructionStatus,\n} from '../stages/compose/instructions';\nimport { getFontConfig, type LocaleCode } from '../stages/compose/font-system';\nimport { filterRenderConfig } from '../utils/object-utils';\n\nexport type ClipUpdateType = 'update' | 'remove';\n\nexport interface ClipUpdateResult {\n clipId: string;\n trackId: string;\n revision: number;\n type: ClipUpdateType;\n instructions?: ClipInstructionSet;\n}\n\nconst DEFAULT_COMPOSITION_WIDTH = 1280;\nconst DEFAULT_COMPOSITION_HEIGHT = 720;\nconst DEFAULT_COMPOSITION_FPS = 30;\n\nconst ATTACHMENT_TYPE_MAP: Record<string, SerializedLayerPlan['type']> = {\n caption: 'text',\n overlay: 'image',\n mask: 'mask',\n};\n\nconst IMAGE_RESOURCE_TYPES = new Set(['image', 'sticker', 'mask']);\n\ninterface ClipPlanResourceRefs {\n pending: Set<string>;\n ready: Set<string>;\n}\n\ninterface ClipPlan {\n clipId: string;\n trackId: string;\n revision: number;\n instructions: ClipInstructionSet;\n resources: ClipPlanResourceRefs;\n}\n\nexport class CompositionPlanner {\n private model: CompositionModel | null = null;\n private readonly clipPlans = new Map<string, ClipPlan>();\n\n setModel(model: CompositionModel): void {\n this.model = model;\n this.clipPlans.clear();\n }\n\n getInstructions(clipId: string): ClipInstructionSet | null {\n const plan = this.clipPlans.get(clipId);\n if (plan) {\n const clip = this.model?.findClip(clipId);\n if (!clip) {\n return plan.instructions;\n }\n if (this.needsPlanRefresh(clip, plan)) {\n const refreshed = this.buildClipPlan(clip, { cache: true });\n return refreshed.instructions;\n }\n return plan.instructions;\n }\n if (!this.model) {\n return null;\n }\n const clip = this.model.findClip(clipId);\n if (!clip) {\n return null;\n }\n const newPlan = this.buildClipPlan(clip, { cache: true });\n return newPlan.instructions;\n }\n\n releaseClip(clipId: string): void {\n this.clipPlans.delete(clipId);\n }\n\n refreshClip(clipId: string): ClipUpdateResult | null {\n if (!this.model) {\n return null;\n }\n\n const clip = this.model.findClip(clipId);\n if (!clip) {\n return null;\n }\n\n const plan = this.buildClipPlan(clip, { cache: true });\n this.clipPlans.set(clipId, plan);\n\n return {\n clipId,\n trackId: clip.trackId as string,\n revision: plan.revision,\n type: 'update',\n instructions: plan.instructions,\n };\n }\n\n /**\n * Apply patch and rebuild instructions for affected clips\n * Simplified for 2-Clip strategy - any change requires pipeline restart\n */\n applyPatch(_patch: CompositionPatch, affectedClipIds: Set<string>): ClipUpdateResult[] {\n if (!this.model) {\n return [];\n }\n const results: ClipUpdateResult[] = [];\n\n // Rebuild instructions for affected clips\n for (const clipId of affectedClipIds) {\n const clip = this.model.findClip(clipId);\n if (!clip) {\n // Clip was removed\n const plan = this.clipPlans.get(clipId);\n this.clipPlans.delete(clipId);\n if (plan) {\n results.push({\n clipId,\n trackId: plan.trackId,\n revision: plan.revision + 1,\n type: 'remove',\n instructions: undefined,\n });\n }\n continue;\n }\n\n // Only video clips need visual composition plans\n // Use both trackKind and track lookup for robustness\n const isVideoClip =\n clip.trackKind === 'video' ||\n (clip.trackId && this.model.findTrack(clip.trackId)?.kind === 'video');\n\n if (!isVideoClip) {\n continue;\n }\n\n // Rebuild plan for existing video clip (any change = pipeline restart)\n const plan = this.buildClipPlan(clip, { cache: false });\n this.clipPlans.set(clip.id, plan);\n\n results.push({\n clipId: clip.id,\n trackId: clip.trackId as string,\n revision: plan.revision,\n type: 'update',\n instructions: plan.instructions,\n });\n }\n\n // Check for orphaned clip plans (clips removed but not in affectedClipIds)\n for (const clipId of this.clipPlans.keys()) {\n if (!this.model.findClip(clipId) && !affectedClipIds.has(clipId)) {\n const plan = this.clipPlans.get(clipId);\n this.clipPlans.delete(clipId);\n if (plan) {\n results.push({\n clipId,\n trackId: plan.trackId,\n revision: plan.revision + 1,\n type: 'remove',\n instructions: undefined,\n });\n }\n }\n }\n\n return results;\n }\n\n buildClipPlan(clip: Clip, options?: { cache?: boolean }): ClipPlan {\n if (!this.model) {\n throw new Error('No composition model set');\n }\n const cache = options?.cache ?? true;\n const previous = this.clipPlans.get(clip.id);\n const revision = (previous?.revision ?? 0) + 1;\n const instructionContext = this.createInstructionSet(clip, revision);\n const plan: ClipPlan = {\n clipId: clip.id,\n trackId: clip.trackId as string,\n revision,\n instructions: instructionContext.instructions,\n resources: instructionContext.resources,\n };\n if (cache) {\n this.clipPlans.set(clip.id, plan);\n }\n return plan;\n }\n\n private createInstructionSet(\n clip: Clip,\n revision: number\n ): { instructions: ClipInstructionSet; resources: ClipPlanResourceRefs } {\n if (!this.model) {\n throw new Error('No composition model set');\n }\n const baseConfig = this.buildBaseConfig(clip);\n const layerResult = this.buildLayerPlans(clip);\n const transitions = this.buildTransitionPlans(clip);\n return {\n instructions: {\n clipId: clip.id,\n trackId: clip.trackId as string,\n revision,\n baseConfig,\n layers: layerResult.layers,\n transitions,\n status: layerResult.status,\n },\n resources: layerResult.resources,\n };\n }\n\n private buildBaseConfig(clip: Clip): VideoComposeConfig {\n const renderConfig = this.model?.renderConfig;\n return {\n width: renderConfig?.width ?? DEFAULT_COMPOSITION_WIDTH,\n height: renderConfig?.height ?? DEFAULT_COMPOSITION_HEIGHT,\n fps: this.model?.fps ?? DEFAULT_COMPOSITION_FPS,\n backgroundColor: renderConfig?.backgroundColor ?? '#000000',\n timeline: {\n clipId: clip.id,\n trackId: clip.trackId ?? 'main',\n clipStartUs: clip.startUs,\n clipDurationUs: clip.durationUs,\n compositionFps: this.model?.fps ?? DEFAULT_COMPOSITION_FPS,\n },\n };\n }\n\n private buildLayerPlans(clip: Clip): {\n layers: SerializedLayerPlan[];\n status: ClipInstructionStatus;\n resources: ClipPlanResourceRefs;\n } {\n const layers: SerializedLayerPlan[] = [];\n const resources: ClipPlanResourceRefs = {\n pending: new Set<string>(),\n ready: new Set<string>(),\n };\n const baseLayer = this.createBaseVideoLayer(clip, resources);\n layers.push(baseLayer);\n const attachments = clip.attachments ?? [];\n\n for (let index = 0; index < attachments.length; index += 1) {\n const attachment = attachments[index];\n if (attachment) {\n const layer = this.attachmentToLayerPlan(clip, attachment, index + 1, resources);\n layers.push(layer);\n }\n }\n // Always ready (resources are preloaded before export)\n return { layers, status: 'ready', resources };\n }\n\n private createBaseVideoLayer(clip: Clip, resources: ClipPlanResourceRefs): SerializedLayerPlan {\n if (!isVideoClip(clip)) {\n throw new Error(`Clip ${clip.id} is not a video clip`);\n }\n const status: ClipInstructionStatus = 'ready';\n this.registerResourceUsage(clip.resourceId, status, resources);\n\n const rate = videoClipPlaybackRate(clip);\n const payload: any = {\n resourceId: clip.resourceId,\n trimStartUs: clip.trimStartUs ?? 0,\n durationUs: clip.durationUs,\n playbackRate: rate,\n };\n\n // Add renderConfig if valid\n const filteredRenderConfig = filterRenderConfig(clip.renderConfig, `video clip ${clip.id}`);\n if (filteredRenderConfig) {\n payload.renderConfig = filteredRenderConfig;\n }\n\n return {\n layerId: `${clip.id}-base-video`,\n type: 'video',\n activeRanges: [\n {\n startUs: 0,\n endUs: clip.durationUs,\n },\n ],\n payload,\n status,\n zIndex: 0,\n };\n }\n\n private attachmentToLayerPlan(\n clip: Clip,\n attachment: Attachment,\n zIndex: number,\n resources: ClipPlanResourceRefs\n ): SerializedLayerPlan {\n const clipDuration = clip.durationUs;\n const startUs = Math.max(0, attachment.startUs);\n const endUs = Math.min(clipDuration, startUs + attachment.durationUs);\n const type = this.resolveAttachmentLayerType(attachment);\n const payload = this.buildAttachmentPayload(attachment, type);\n // Always mark as ready for export (resources are preloaded)\n const status: ClipInstructionStatus = 'ready';\n const resourceId = (payload as any).resourceId;\n if (resourceId && typeof resourceId === 'string') {\n this.registerResourceUsage(resourceId, status, resources);\n }\n\n return {\n layerId: `${clip.id}-attachment-${attachment.id}`,\n type,\n activeRanges: [\n {\n startUs,\n endUs,\n },\n ],\n payload,\n status,\n zIndex,\n } as SerializedLayerPlan;\n }\n\n private resolveAttachmentLayerType(attachment: Attachment): SerializedLayerPlan['type'] {\n const mappedType = ATTACHMENT_TYPE_MAP[attachment.kind];\n if (mappedType) {\n return mappedType;\n }\n if (typeof attachment.data.text === 'string') {\n return 'text';\n }\n if (attachment.data.resourceId) {\n const resource = this.model?.getResource(attachment.data.resourceId as string);\n if (resource && IMAGE_RESOURCE_TYPES.has(resource.type)) {\n return 'image';\n }\n }\n return 'effect';\n }\n\n private buildAttachmentPayload(\n attachment: Attachment,\n type: SerializedLayerPlan['type']\n ): SerializedLayerPlan['payload'] {\n const basePayload: Record<string, unknown> = {\n ...attachment.data,\n attachmentId: attachment.id,\n };\n if (type === 'text') {\n const text = this.getStringField(attachment.data, 'text') || '';\n let localeCode = this.getStringField(attachment.data, 'localeCode') as LocaleCode | undefined;\n let fontTemplate = this.getStringField(attachment.data, 'fontTemplate');\n const fontFamily = this.getStringField(attachment.data, 'fontFamily');\n const animation = attachment.data.animation as CaptionAttachmentData['animation'] | undefined;\n const letterCase = this.getStringField(attachment.data, 'letterCase') as\n | 'upper'\n | 'lower'\n | 'none'\n | undefined;\n const wordTimings = attachment.data.wordTimings as\n | CaptionAttachmentData['wordTimings']\n | undefined;\n\n if (!localeCode) {\n localeCode = 'en-US';\n }\n\n if (!fontTemplate) {\n fontTemplate = this.getDefaultFontTemplate(localeCode);\n }\n\n const fontConfig = getFontConfig(localeCode, fontTemplate, fontFamily);\n\n const payload: SerializedTextLayerPayload = {\n text,\n localeCode,\n fontConfig,\n letterCase,\n wordTimings,\n ...basePayload,\n };\n\n if (animation) {\n payload.animation = {\n ...animation,\n type: animation.type,\n glowColor: animation.glowColor as string | undefined,\n glowIntensity: animation.glowIntensity as number | undefined,\n transitionFrames: animation.transitionFrames as number | undefined,\n highlightColor: animation.highlightColor as string | undefined,\n };\n }\n\n return payload;\n }\n if (type === 'image') {\n const imagePayload: SerializedImageLayerPayload = {\n ...basePayload,\n resourceId: this.getStringField(attachment.data, 'resourceId') || '',\n } as SerializedImageLayerPayload;\n\n // Add renderConfig if valid\n const filteredRenderConfig = filterRenderConfig(\n attachment.data.renderConfig as any,\n `image attachment ${attachment.id}`\n );\n if (filteredRenderConfig) {\n imagePayload.renderConfig = filteredRenderConfig;\n }\n\n // Add animation config for overlay attachments\n if (attachment.kind === 'overlay' && attachment.data.animation) {\n imagePayload.animation = {\n ...attachment.data.animation,\n overlayClipStartUs: attachment.data.overlayClipStartUs,\n mainClipStartUs: attachment.data.mainClipStartUs,\n } as any;\n }\n\n return imagePayload;\n }\n if (type === 'mask') {\n return {\n ...basePayload,\n resourceId: this.getStringField(attachment.data, 'resourceId'),\n } as SerializedMaskLayerPayload;\n }\n return {\n ...basePayload,\n } as SerializedEffectLayerPayload;\n }\n\n private registerResourceUsage(\n identifier: string,\n status: ClipInstructionStatus,\n resources: ClipPlanResourceRefs\n ): void {\n if (!identifier) {\n return;\n }\n if (status === 'ready') {\n resources.ready.add(identifier);\n } else {\n resources.pending.add(identifier);\n }\n }\n\n private getDefaultFontTemplate(_locale: LocaleCode): string {\n // Select template based on canvas aspect ratio\n const width = this.model?.renderConfig?.width || DEFAULT_COMPOSITION_WIDTH;\n const height = this.model?.renderConfig?.height || DEFAULT_COMPOSITION_HEIGHT;\n\n // Landscape (16:9) vs Portrait (9:16)\n const isLandscape = width > height;\n return isLandscape ? 'baseSubtitle16_9' : 'baseSubtitle';\n }\n\n private buildTransitionPlans(clip: Clip): SerializedTransitionPlan[] {\n const transitions: SerializedTransitionPlan[] = [];\n const track = clip.trackId ? this.model?.findTrack(clip.trackId) : null;\n if (clip.transitionIn) {\n transitions.push(this.transitionToPlan(clip.transitionIn, 0, clip.durationUs));\n }\n if (clip.transitionOut) {\n const startUs = Math.max(0, clip.durationUs - clip.transitionOut.durationUs);\n transitions.push(this.transitionToPlan(clip.transitionOut, startUs, clip.durationUs));\n }\n if (track?.effects?.length) {\n for (const effect of track.effects) {\n transitions.push({\n transitionId: effect.id,\n range: {\n startUs: 0,\n endUs: clip.durationUs,\n },\n params: {\n type: effect.effectType,\n easing: effect.params?.easing as string | undefined,\n durationUs: effect.params?.durationUs as TimeUs | undefined,\n payload: effect.params,\n },\n });\n }\n }\n return transitions;\n }\n\n private transitionToPlan(\n transition: Transition,\n startUs: TimeUs,\n clipDurationUs: TimeUs\n ): SerializedTransitionPlan {\n const duration = Math.min(transition.durationUs, clipDurationUs);\n const clampedStart = Math.max(0, Math.min(startUs, clipDurationUs));\n const clampedEnd = Math.min(clampedStart + duration, clipDurationUs);\n return {\n transitionId: transition.id,\n range: {\n startUs: clampedStart,\n endUs: clampedEnd,\n },\n params: {\n type: transition.transitionType,\n ...transition.params,\n },\n };\n }\n\n private getStringField(data: Record<string, unknown>, key: string): string | undefined {\n const value = data[key];\n return typeof value === 'string' ? value : undefined;\n }\n\n private needsPlanRefresh(clip: Clip, plan: ClipPlan): boolean {\n // Check if attachments count changed\n const currentAttachmentCount = (clip.attachments ?? []).length;\n const cachedAttachmentCount = plan.instructions.layers.filter(\n (layer) => layer.payload.attachmentId\n ).length;\n return currentAttachmentCount !== cachedAttachmentCount;\n }\n}\n"],"names":["clip","plan","isVideoClip"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExportScheduler.d.ts","sourceRoot":"","sources":["../../src/orchestrator/ExportScheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAQ,MAAM,UAAU,CAAC;AAClD,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;
|
|
1
|
+
{"version":3,"file":"ExportScheduler.d.ts","sourceRoot":"","sources":["../../src/orchestrator/ExportScheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAQ,MAAM,UAAU,CAAC;AAClD,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;AAQ7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAExE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAgB,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAKhE,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;IAC9B,UAAU,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC/B,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAKD,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,GAAG,IAAI,CAAC;YAe9E,eAAe;YA8If,gBAAgB;YAyDhB,qBAAqB;IAwBnC;;;OAGG;YACW,oBAAoB;YAyIpB,wBAAwB;YAyDxB,gBAAgB;IA8B9B,OAAO,CAAC,qBAAqB;YAKf,0BAA0B;IA2BxC,OAAO,CAAC,+BAA+B;YAazB,mBAAmB;CA0ClC"}
|