@meframe/core 0.0.45 → 0.0.46
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/controllers/PlaybackController.d.ts +0 -1
- package/dist/controllers/PlaybackController.d.ts.map +1 -1
- package/dist/controllers/PlaybackController.js +4 -8
- package/dist/controllers/PlaybackController.js.map +1 -1
- package/dist/model/CompositionModel.d.ts.map +1 -1
- package/dist/model/CompositionModel.js +3 -2
- package/dist/model/CompositionModel.js.map +1 -1
- package/dist/orchestrator/CompositionPlanner.d.ts +0 -3
- package/dist/orchestrator/CompositionPlanner.d.ts.map +1 -1
- package/dist/orchestrator/CompositionPlanner.js +10 -43
- package/dist/orchestrator/CompositionPlanner.js.map +1 -1
- package/dist/orchestrator/ExportScheduler.d.ts.map +1 -1
- package/dist/orchestrator/ExportScheduler.js +2 -12
- package/dist/orchestrator/ExportScheduler.js.map +1 -1
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +0 -1
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/orchestrator/VideoClipSession.d.ts +5 -1
- package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
- package/dist/orchestrator/VideoClipSession.js +38 -10
- package/dist/orchestrator/VideoClipSession.js.map +1 -1
- package/dist/stages/load/ResourceLoader.d.ts +7 -19
- package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.js +15 -98
- package/dist/stages/load/ResourceLoader.js.map +1 -1
- package/dist/stages/load/types.d.ts +0 -2
- package/dist/stages/load/types.d.ts.map +1 -1
- package/dist/workers/stages/compose/{video-compose.worker.CeT5le4s.js → video-compose.worker.BP7fEC58.js} +1 -7
- package/dist/workers/stages/compose/{video-compose.worker.CeT5le4s.js.map → video-compose.worker.BP7fEC58.js.map} +1 -1
- package/dist/workers/worker-manifest.json +1 -1
- package/package.json +1 -1
|
@@ -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 Resource,\n CaptionAttachmentData,\n} from '../model';\nimport { hasResourceId, 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';\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 const status = this.computeInstructionStatus(layerResult.layers);\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,\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 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 const overallStatus = this.computeInstructionStatus(layers);\n return { layers, status: overallStatus, 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 resourceState = this.getResourceState(clip.resourceId);\n const status: ClipInstructionStatus = resourceState === 'ready' ? 'ready' : 'pending';\n this.registerResourceUsage(clip.resourceId, status, resources);\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 resourceId: clip.resourceId,\n trimStartUs: clip.trimStartUs ?? 0,\n durationUs: clip.durationUs,\n },\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 const resourceState = this.resolveAttachmentResourceState(attachment, type);\n const status: ClipInstructionStatus = resourceState === 'ready' ? 'ready' : 'pending';\n const resourceId = (payload as any).resourceId;\n if (resourceId && typeof resourceId === 'string') {\n this.registerResourceUsage(resourceId, status, resources);\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 target dimensions if specified (supports both number and string for percentages)\n if (attachment.data.targetWidth !== undefined) {\n const targetWidth = attachment.data.targetWidth;\n imagePayload.targetWidth =\n typeof targetWidth === 'string'\n ? targetWidth\n : (this.getNumberField(attachment.data, 'targetWidth') ?? undefined);\n }\n if (attachment.data.targetHeight !== undefined) {\n const targetHeight = attachment.data.targetHeight;\n imagePayload.targetHeight =\n typeof targetHeight === 'string'\n ? targetHeight\n : (this.getNumberField(attachment.data, 'targetHeight') ?? undefined);\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 resolveAttachmentResourceState(\n attachment: Attachment,\n type: SerializedLayerPlan['type']\n ): 'ready' | 'pending' {\n if (type === 'text') {\n return 'ready';\n }\n const resourceId = this.getStringField(attachment.data, 'resourceId');\n if (!resourceId) {\n return 'pending';\n }\n const resourceState = this.getResourceState(resourceId);\n return resourceState === 'ready' ? 'ready' : 'pending';\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 getResourceState(resourceId: string): Resource['state'] | 'pending' {\n const resource = this.model?.getResource(resourceId);\n return resource?.state ?? 'pending';\n }\n\n private computeInstructionStatus(layers: SerializedLayerPlan[]): ClipInstructionStatus {\n return layers.some((layer) => layer.status === 'pending') ? 'pending' : 'ready';\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 getNumberField(data: Record<string, unknown>, key: string): number | undefined {\n const value = data[key];\n return typeof value === 'number' ? value : undefined;\n }\n\n private needsPlanRefresh(clip: Clip, plan: ClipPlan): boolean {\n const baseLayer = plan.instructions.layers.find((layer) => layer.type === 'video');\n if (hasResourceId(clip)) {\n const currentBaseState = this.getResourceState(clip.resourceId);\n if (baseLayer && baseLayer.status !== (currentBaseState === 'ready' ? 'ready' : 'pending')) {\n return true;\n }\n }\n\n if (plan.resources.pending.size === 0) {\n return false;\n }\n\n for (const resourceId of plan.resources.pending) {\n if (this.getResourceState(resourceId) === 'ready') {\n return true;\n }\n }\n\n return false;\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 } 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';\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 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 resourceId: clip.resourceId,\n trimStartUs: clip.trimStartUs ?? 0,\n durationUs: clip.durationUs,\n },\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 target dimensions if specified (supports both number and string for percentages)\n if (attachment.data.targetWidth !== undefined) {\n const targetWidth = attachment.data.targetWidth;\n imagePayload.targetWidth =\n typeof targetWidth === 'string'\n ? targetWidth\n : (this.getNumberField(attachment.data, 'targetWidth') ?? undefined);\n }\n if (attachment.data.targetHeight !== undefined) {\n const targetHeight = attachment.data.targetHeight;\n imagePayload.targetHeight =\n typeof targetHeight === 'string'\n ? targetHeight\n : (this.getNumberField(attachment.data, 'targetHeight') ?? undefined);\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 getNumberField(data: Record<string, unknown>, key: string): number | undefined {\n const value = data[key];\n return typeof value === 'number' ? 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,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAgB,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEhE,UAAU,mBAAmB;IAC3B,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,EAAE,kBAAkB,CAAC;IAC5B,YAAY,EAAE,YAAY,CAAC;IAC3B,cAAc,EAAE,cAAc,CAAC;IAC/B,UAAU,EAAE,UAAU,CAAC;IACvB,YAAY,EAAE,kBAAkB,CAAC;IACjC,qBAAqB,EAAE,MAAM,MAAM,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IACrD,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;CACrC;AAED,UAAU,qBAAsB,SAAQ,aAAa;IACnD,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,IAAI,CAAsB;gBAEtB,IAAI,EAAE,mBAAmB;IAI/B,OAAO,CAAC,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;YAWvE,eAAe;IA0F7B;;OAEG;YACW,gBAAgB;IAgD9B;;;;OAIG;YACW,qBAAqB;YAsBrB,6BAA6B;
|
|
1
|
+
{"version":3,"file":"ExportScheduler.d.ts","sourceRoot":"","sources":["../../src/orchestrator/ExportScheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAgB,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEhE,UAAU,mBAAmB;IAC3B,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,EAAE,kBAAkB,CAAC;IAC5B,YAAY,EAAE,YAAY,CAAC;IAC3B,cAAc,EAAE,cAAc,CAAC;IAC/B,UAAU,EAAE,UAAU,CAAC;IACvB,YAAY,EAAE,kBAAkB,CAAC;IACjC,qBAAqB,EAAE,MAAM,MAAM,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IACrD,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;CACrC;AAED,UAAU,qBAAsB,SAAQ,aAAa;IACnD,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,IAAI,CAAsB;gBAEtB,IAAI,EAAE,mBAAmB;IAI/B,OAAO,CAAC,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;YAWvE,eAAe;IA0F7B;;OAEG;YACW,gBAAgB;IAgD9B;;;;OAIG;YACW,qBAAqB;YAsBrB,6BAA6B;CA0G5C"}
|
|
@@ -199,19 +199,9 @@ class ExportScheduler {
|
|
|
199
199
|
reader.releaseLock();
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
|
-
},
|
|
203
|
-
onPipelineReady: async (attachmentResourceIds) => {
|
|
204
|
-
if (attachmentResourceIds) {
|
|
205
|
-
for (const rid of attachmentResourceIds) {
|
|
206
|
-
await this.deps.resourceLoader.load(rid, {
|
|
207
|
-
isPreload: false,
|
|
208
|
-
sessionId,
|
|
209
|
-
clipId: clip.id,
|
|
210
|
-
isMainTrack: false
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
202
|
}
|
|
203
|
+
// Note: Attachment resources are loaded in VideoClipSession.connectPipeline
|
|
204
|
+
// before sending video stream, ensuring watermarks appear from the first frame
|
|
215
205
|
}
|
|
216
206
|
});
|
|
217
207
|
await session.activate();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExportScheduler.js","sources":["../../src/orchestrator/ExportScheduler.ts"],"sourcesContent":["import { CompositionModel } from '../model';\nimport { ExportOptions } from '../types';\nimport { WorkerPool } from '../worker/WorkerPool';\nimport { CompositionPlanner } from './CompositionPlanner';\nimport { CacheManager } from '../cache/CacheManager';\nimport { ResourceLoader } from '../stages/load/ResourceLoader';\nimport { MuxManager } from '../stages/mux/MuxManager';\nimport { GlobalAudioSession } from './GlobalAudioSession';\nimport { VideoClipSession } from './VideoClipSession';\nimport { WorkerType } from '../worker/types';\nimport { hasResourceId, type TimeUs } from '../model/types';\nimport type { ExportController } from '../controllers/ExportController';\nimport { EventBus } from '../event/EventBus';\nimport { MeframeEvent, EventPayloadMap } from '../event/events';\n\ninterface ExportSchedulerDeps {\n workerPool: WorkerPool;\n planner: CompositionPlanner;\n cacheManager: CacheManager;\n resourceLoader: ResourceLoader;\n muxManager: MuxManager;\n audioSession: GlobalAudioSession;\n workerConfigsProvider: () => Record<WorkerType, any>;\n eventBus: EventBus<EventPayloadMap>;\n}\n\ninterface ExtendedExportOptions extends ExportOptions {\n signal?: AbortSignal;\n controller?: ExportController;\n}\n\nexport class ExportScheduler {\n private deps: ExportSchedulerDeps;\n\n constructor(deps: ExportSchedulerDeps) {\n this.deps = deps;\n }\n\n async execute(model: CompositionModel, options: ExtendedExportOptions): Promise<Blob> {\n const projectId = this.deps.cacheManager.resourceCache.projectId;\n\n if (!navigator.locks) {\n return this.executeInternal(model, options);\n }\n\n const lockName = `meframe-resource-${projectId}`;\n return navigator.locks.request(lockName, () => this.executeInternal(model, options));\n }\n\n private async executeInternal(\n model: CompositionModel,\n options: ExtendedExportOptions\n ): Promise<Blob> {\n const { muxManager, audioSession, eventBus, resourceLoader } = this.deps;\n const signal = options.signal;\n const controller = options.controller;\n\n const checkStatus = async () => {\n if (signal?.aborted) {\n throw new DOMException('Export aborted', 'AbortError');\n }\n if (controller?.isPaused()) {\n // Wait until resumed\n while (controller.isPaused()) {\n if (signal?.aborted) throw new DOMException('Export aborted', 'AbortError');\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n }\n };\n\n const width = options.width || model.renderConfig?.width || 720;\n const height = options.height || model.renderConfig?.height || 1280;\n const fps = options.fps || model.fps || 30;\n\n eventBus.emit(MeframeEvent.ExportStart, {\n format: options.format || 'mp4',\n width,\n height,\n fps,\n durationUs: model.durationUs,\n });\n\n try {\n // 1. Preload and parse all resources (0-40%)\n await this.preloadResources(model, resourceLoader, eventBus, checkStatus);\n\n // 2. Start Muxer\n muxManager.start({\n width,\n height,\n fps,\n });\n\n // 3. Process Video and Audio\n const mainTrack = model.tracks.find((t) => t.id === model.mainTrackId);\n if (mainTrack && mainTrack.clips.length > 0) {\n // Process audio with 60s windows (balance quality and memory)\n const audioPromise = this.processAudioInWindows(\n model.durationUs,\n audioSession,\n muxManager,\n checkStatus\n );\n\n // Process video clips sequentially\n await this.processVideoClipsSequentially(mainTrack.clips, muxManager, model, checkStatus);\n\n // Wait for audio encoding to complete\n await audioPromise;\n } else {\n console.warn('[ExportScheduler] No video clips found');\n }\n\n // Finalize audio session (close encoder)\n await audioSession.finalizeExportAudio();\n\n if (signal?.aborted) {\n throw new DOMException('Export aborted', 'AbortError');\n }\n\n // 4. Finalize\n const blob = await muxManager.finalize();\n\n eventBus.emit(MeframeEvent.ExportComplete, {\n size: blob.size,\n durationMs: model.durationUs / 1000,\n format: options.format || 'mp4',\n });\n\n return blob;\n } catch (error) {\n eventBus.emit(MeframeEvent.ExportError, {\n error: error instanceof Error ? error : new Error(String(error)),\n stage: 'export',\n });\n throw error;\n }\n }\n\n /**\n * Preload all resources (0-40% progress)\n */\n private async preloadResources(\n model: CompositionModel,\n resourceLoader: ResourceLoader,\n eventBus: EventBus<EventPayloadMap>,\n checkStatus: () => Promise<void>\n ): Promise<void> {\n eventBus.emit(MeframeEvent.ExportProgress, {\n progress: 0,\n stage: 'preparing',\n message: 'Loading and parsing resources...',\n });\n\n // Collect resources in horizontal order (clip index priority)\n const tracks = model.tracks.filter((track) => ['video', 'audio'].includes(track.kind));\n if (tracks.length === 0) return;\n\n const maxClipCount = Math.max(...tracks.map((track) => track.clips.length));\n const resourcesToLoad: string[] = [];\n\n // Horizontal collection: clip[0] from all tracks, then clip[1], etc.\n for (let clipIndex = 0; clipIndex < maxClipCount; clipIndex++) {\n for (const track of tracks) {\n const clip = track.clips[clipIndex];\n if (clip && hasResourceId(clip)) {\n resourcesToLoad.push(clip.resourceId);\n }\n }\n }\n\n // Load resources with progress updates\n for (let i = 0; i < resourcesToLoad.length; i++) {\n await checkStatus();\n\n const resourceId = resourcesToLoad[i];\n if (!resourceId) continue;\n\n await resourceLoader.load(resourceId, { isPreload: false });\n\n // Update progress: 0-40%\n const progress = ((i + 1) / resourcesToLoad.length) * 0.4;\n eventBus.emit(MeframeEvent.ExportProgress, {\n progress,\n stage: 'preparing',\n message: `Loading resources... (${i + 1}/${resourcesToLoad.length})`,\n });\n }\n }\n\n /**\n * Process audio in 60-second windows\n * - Videos ≤60s: Single pass (zero boundaries)\n * - Videos >60s: 60s windows (minimal boundaries, ~23MB per window)\n */\n private async processAudioInWindows(\n totalDurationUs: TimeUs,\n audioSession: GlobalAudioSession,\n muxManager: MuxManager,\n checkStatus: () => Promise<void>\n ): Promise<void> {\n const WINDOW_DURATION_US = 5 * 60 * 1_000_000; // 5 minutes\n let currentUs = 0;\n\n while (currentUs < totalDurationUs) {\n await checkStatus();\n\n const endUs = Math.min(currentUs + WINDOW_DURATION_US, totalDurationUs);\n\n await audioSession.mixAndEncodeSegment(currentUs, endUs, (chunk, meta) =>\n muxManager.writeAudioChunk(chunk, meta)\n );\n\n currentUs = endUs;\n }\n }\n\n private async processVideoClipsSequentially(\n clips: any[],\n muxManager: MuxManager,\n model: CompositionModel,\n checkStatus: () => Promise<void>\n ) {\n // Use actual last written timestamp + duration as offset for next clip\n // This avoids duplicate PTS when mp4-muxer rounds microseconds to timescale\n let nextClipStartUs = 0;\n // Track last chunk's end time (timestamp + duration) for precise offset calculation\n let lastChunkEndUs = 0;\n\n for (let i = 0; i < clips.length; i++) {\n const clip = clips[i];\n const currentClipOffsetUs = nextClipStartUs;\n\n await checkStatus(); // Check before starting new clip\n\n const sessionId = `${clip.id}-export`;\n let streamFinishedResolver: () => void;\n let streamFinishedRejecter: (err: any) => void;\n const streamFinishedPromise = new Promise<void>((resolve, reject) => {\n streamFinishedResolver = resolve;\n streamFinishedRejecter = reject;\n });\n\n const session = await VideoClipSession.create({\n clipId: clip.id,\n sessionId,\n planner: this.deps.planner,\n workerPool: this.deps.workerPool,\n cacheManager: this.deps.cacheManager,\n compositionModel: model,\n workerConfigs: this.deps.workerConfigsProvider(),\n resourceLoader: this.deps.resourceLoader,\n callbacks: {\n onEncodedStreamReady: async (stream, track) => {\n if (track === 'video') {\n const reader = stream.getReader();\n try {\n while (true) {\n await checkStatus();\n\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n const originalChunk = value.chunk;\n const metadata = value.metadata;\n const chunkDuration = originalChunk.duration ?? 33333; // Default ~30fps\n\n const remappedTimestamp = originalChunk.timestamp + currentClipOffsetUs;\n\n const buffer = new ArrayBuffer(originalChunk.byteLength);\n originalChunk.copyTo(buffer);\n\n const remappedChunk = new EncodedVideoChunk({\n type: originalChunk.type,\n timestamp: remappedTimestamp,\n duration: chunkDuration,\n data: buffer,\n });\n\n muxManager.writeVideoChunk(remappedChunk, metadata);\n\n // Track end time for next clip's offset\n lastChunkEndUs = remappedTimestamp + chunkDuration;\n\n // Emit progress: 40-100%\n const encodingProgress = remappedTimestamp / model.durationUs;\n const totalProgress = 0.4 + encodingProgress * 0.6; // 40% + (0-60%)\n\n this.deps.eventBus.emit(MeframeEvent.ExportProgress, {\n progress: Math.min(1.0, totalProgress),\n stage: 'encoding',\n timeUs: remappedTimestamp,\n });\n }\n }\n streamFinishedResolver();\n } catch (error) {\n if (error instanceof DOMException && error.name === 'AbortError') {\n streamFinishedRejecter(error);\n } else {\n console.error(`[ExportScheduler] Stream error for clip ${clip.id}:`, error);\n streamFinishedRejecter(error);\n }\n } finally {\n reader.releaseLock();\n }\n }\n },\n onPipelineReady: async (attachmentResourceIds) => {\n // Load Attachment Resources only\n // Main track resources are loaded during:\n // - Video: preloadResources phase (before pipeline creation)\n // - Image: connectImagePipeline (parallel with worker setup)\n if (attachmentResourceIds) {\n for (const rid of attachmentResourceIds) {\n await this.deps.resourceLoader.load(rid, {\n isPreload: false,\n sessionId,\n clipId: clip.id,\n isMainTrack: false,\n });\n }\n }\n },\n },\n });\n\n await session.activate();\n await streamFinishedPromise;\n\n await session.dispose();\n\n // Use actual last chunk end time as next clip's start\n // This ensures no timestamp overlap even after muxer rounding\n nextClipStartUs = lastChunkEndUs;\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ExportScheduler.js","sources":["../../src/orchestrator/ExportScheduler.ts"],"sourcesContent":["import { CompositionModel } from '../model';\nimport { ExportOptions } from '../types';\nimport { WorkerPool } from '../worker/WorkerPool';\nimport { CompositionPlanner } from './CompositionPlanner';\nimport { CacheManager } from '../cache/CacheManager';\nimport { ResourceLoader } from '../stages/load/ResourceLoader';\nimport { MuxManager } from '../stages/mux/MuxManager';\nimport { GlobalAudioSession } from './GlobalAudioSession';\nimport { VideoClipSession } from './VideoClipSession';\nimport { WorkerType } from '../worker/types';\nimport { hasResourceId, type TimeUs } from '../model/types';\nimport type { ExportController } from '../controllers/ExportController';\nimport { EventBus } from '../event/EventBus';\nimport { MeframeEvent, EventPayloadMap } from '../event/events';\n\ninterface ExportSchedulerDeps {\n workerPool: WorkerPool;\n planner: CompositionPlanner;\n cacheManager: CacheManager;\n resourceLoader: ResourceLoader;\n muxManager: MuxManager;\n audioSession: GlobalAudioSession;\n workerConfigsProvider: () => Record<WorkerType, any>;\n eventBus: EventBus<EventPayloadMap>;\n}\n\ninterface ExtendedExportOptions extends ExportOptions {\n signal?: AbortSignal;\n controller?: ExportController;\n}\n\nexport class ExportScheduler {\n private deps: ExportSchedulerDeps;\n\n constructor(deps: ExportSchedulerDeps) {\n this.deps = deps;\n }\n\n async execute(model: CompositionModel, options: ExtendedExportOptions): Promise<Blob> {\n const projectId = this.deps.cacheManager.resourceCache.projectId;\n\n if (!navigator.locks) {\n return this.executeInternal(model, options);\n }\n\n const lockName = `meframe-resource-${projectId}`;\n return navigator.locks.request(lockName, () => this.executeInternal(model, options));\n }\n\n private async executeInternal(\n model: CompositionModel,\n options: ExtendedExportOptions\n ): Promise<Blob> {\n const { muxManager, audioSession, eventBus, resourceLoader } = this.deps;\n const signal = options.signal;\n const controller = options.controller;\n\n const checkStatus = async () => {\n if (signal?.aborted) {\n throw new DOMException('Export aborted', 'AbortError');\n }\n if (controller?.isPaused()) {\n // Wait until resumed\n while (controller.isPaused()) {\n if (signal?.aborted) throw new DOMException('Export aborted', 'AbortError');\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n }\n };\n\n const width = options.width || model.renderConfig?.width || 720;\n const height = options.height || model.renderConfig?.height || 1280;\n const fps = options.fps || model.fps || 30;\n\n eventBus.emit(MeframeEvent.ExportStart, {\n format: options.format || 'mp4',\n width,\n height,\n fps,\n durationUs: model.durationUs,\n });\n\n try {\n // 1. Preload and parse all resources (0-40%)\n await this.preloadResources(model, resourceLoader, eventBus, checkStatus);\n\n // 2. Start Muxer\n muxManager.start({\n width,\n height,\n fps,\n });\n\n // 3. Process Video and Audio\n const mainTrack = model.tracks.find((t) => t.id === model.mainTrackId);\n if (mainTrack && mainTrack.clips.length > 0) {\n // Process audio with 60s windows (balance quality and memory)\n const audioPromise = this.processAudioInWindows(\n model.durationUs,\n audioSession,\n muxManager,\n checkStatus\n );\n\n // Process video clips sequentially\n await this.processVideoClipsSequentially(mainTrack.clips, muxManager, model, checkStatus);\n\n // Wait for audio encoding to complete\n await audioPromise;\n } else {\n console.warn('[ExportScheduler] No video clips found');\n }\n\n // Finalize audio session (close encoder)\n await audioSession.finalizeExportAudio();\n\n if (signal?.aborted) {\n throw new DOMException('Export aborted', 'AbortError');\n }\n\n // 4. Finalize\n const blob = await muxManager.finalize();\n\n eventBus.emit(MeframeEvent.ExportComplete, {\n size: blob.size,\n durationMs: model.durationUs / 1000,\n format: options.format || 'mp4',\n });\n\n return blob;\n } catch (error) {\n eventBus.emit(MeframeEvent.ExportError, {\n error: error instanceof Error ? error : new Error(String(error)),\n stage: 'export',\n });\n throw error;\n }\n }\n\n /**\n * Preload all resources (0-40% progress)\n */\n private async preloadResources(\n model: CompositionModel,\n resourceLoader: ResourceLoader,\n eventBus: EventBus<EventPayloadMap>,\n checkStatus: () => Promise<void>\n ): Promise<void> {\n eventBus.emit(MeframeEvent.ExportProgress, {\n progress: 0,\n stage: 'preparing',\n message: 'Loading and parsing resources...',\n });\n\n // Collect resources in horizontal order (clip index priority)\n const tracks = model.tracks.filter((track) => ['video', 'audio'].includes(track.kind));\n if (tracks.length === 0) return;\n\n const maxClipCount = Math.max(...tracks.map((track) => track.clips.length));\n const resourcesToLoad: string[] = [];\n\n // Horizontal collection: clip[0] from all tracks, then clip[1], etc.\n for (let clipIndex = 0; clipIndex < maxClipCount; clipIndex++) {\n for (const track of tracks) {\n const clip = track.clips[clipIndex];\n if (clip && hasResourceId(clip)) {\n resourcesToLoad.push(clip.resourceId);\n }\n }\n }\n\n // Load resources with progress updates\n for (let i = 0; i < resourcesToLoad.length; i++) {\n await checkStatus();\n\n const resourceId = resourcesToLoad[i];\n if (!resourceId) continue;\n\n await resourceLoader.load(resourceId, { isPreload: false });\n\n // Update progress: 0-40%\n const progress = ((i + 1) / resourcesToLoad.length) * 0.4;\n eventBus.emit(MeframeEvent.ExportProgress, {\n progress,\n stage: 'preparing',\n message: `Loading resources... (${i + 1}/${resourcesToLoad.length})`,\n });\n }\n }\n\n /**\n * Process audio in 60-second windows\n * - Videos ≤60s: Single pass (zero boundaries)\n * - Videos >60s: 60s windows (minimal boundaries, ~23MB per window)\n */\n private async processAudioInWindows(\n totalDurationUs: TimeUs,\n audioSession: GlobalAudioSession,\n muxManager: MuxManager,\n checkStatus: () => Promise<void>\n ): Promise<void> {\n const WINDOW_DURATION_US = 5 * 60 * 1_000_000; // 5 minutes\n let currentUs = 0;\n\n while (currentUs < totalDurationUs) {\n await checkStatus();\n\n const endUs = Math.min(currentUs + WINDOW_DURATION_US, totalDurationUs);\n\n await audioSession.mixAndEncodeSegment(currentUs, endUs, (chunk, meta) =>\n muxManager.writeAudioChunk(chunk, meta)\n );\n\n currentUs = endUs;\n }\n }\n\n private async processVideoClipsSequentially(\n clips: any[],\n muxManager: MuxManager,\n model: CompositionModel,\n checkStatus: () => Promise<void>\n ) {\n // Use actual last written timestamp + duration as offset for next clip\n // This avoids duplicate PTS when mp4-muxer rounds microseconds to timescale\n let nextClipStartUs = 0;\n // Track last chunk's end time (timestamp + duration) for precise offset calculation\n let lastChunkEndUs = 0;\n\n for (let i = 0; i < clips.length; i++) {\n const clip = clips[i];\n const currentClipOffsetUs = nextClipStartUs;\n\n await checkStatus(); // Check before starting new clip\n\n const sessionId = `${clip.id}-export`;\n let streamFinishedResolver: () => void;\n let streamFinishedRejecter: (err: any) => void;\n const streamFinishedPromise = new Promise<void>((resolve, reject) => {\n streamFinishedResolver = resolve;\n streamFinishedRejecter = reject;\n });\n\n const session = await VideoClipSession.create({\n clipId: clip.id,\n sessionId,\n planner: this.deps.planner,\n workerPool: this.deps.workerPool,\n cacheManager: this.deps.cacheManager,\n compositionModel: model,\n workerConfigs: this.deps.workerConfigsProvider(),\n resourceLoader: this.deps.resourceLoader,\n callbacks: {\n onEncodedStreamReady: async (stream, track) => {\n if (track === 'video') {\n const reader = stream.getReader();\n try {\n while (true) {\n await checkStatus();\n\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n const originalChunk = value.chunk;\n const metadata = value.metadata;\n const chunkDuration = originalChunk.duration ?? 33333; // Default ~30fps\n\n const remappedTimestamp = originalChunk.timestamp + currentClipOffsetUs;\n\n const buffer = new ArrayBuffer(originalChunk.byteLength);\n originalChunk.copyTo(buffer);\n\n const remappedChunk = new EncodedVideoChunk({\n type: originalChunk.type,\n timestamp: remappedTimestamp,\n duration: chunkDuration,\n data: buffer,\n });\n\n muxManager.writeVideoChunk(remappedChunk, metadata);\n\n // Track end time for next clip's offset\n lastChunkEndUs = remappedTimestamp + chunkDuration;\n\n // Emit progress: 40-100%\n const encodingProgress = remappedTimestamp / model.durationUs;\n const totalProgress = 0.4 + encodingProgress * 0.6; // 40% + (0-60%)\n\n this.deps.eventBus.emit(MeframeEvent.ExportProgress, {\n progress: Math.min(1.0, totalProgress),\n stage: 'encoding',\n timeUs: remappedTimestamp,\n });\n }\n }\n streamFinishedResolver();\n } catch (error) {\n if (error instanceof DOMException && error.name === 'AbortError') {\n streamFinishedRejecter(error);\n } else {\n console.error(`[ExportScheduler] Stream error for clip ${clip.id}:`, error);\n streamFinishedRejecter(error);\n }\n } finally {\n reader.releaseLock();\n }\n }\n },\n // Note: Attachment resources are loaded in VideoClipSession.connectPipeline\n // before sending video stream, ensuring watermarks appear from the first frame\n },\n });\n\n await session.activate();\n await streamFinishedPromise;\n\n await session.dispose();\n\n // Use actual last chunk end time as next clip's start\n // This ensures no timestamp overlap even after muxer rounding\n nextClipStartUs = lastChunkEndUs;\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Orchestrator.d.ts","sourceRoot":"","sources":["../../src/orchestrator/Orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAErF,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAY,MAAM,EAAE,OAAO,EAAQ,MAAM,UAAU,CAAC;AAE/F,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,qBAAa,YAAa,YAAW,aAAa;IAChD,OAAO,EAAE,UAAU,CAAC;IACpB,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;IACpC,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAQ;IACjD,cAAc,EAAE,cAAc,CAAC;IAC/B,YAAY,EAAE,YAAY,CAAC;IAC3B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,YAAY,EAAE,kBAAkB,CAAC;IACjC,UAAU,EAAE,UAAU,CAAC;IACvB,eAAe,EAAE,eAAe,CAAC;IAEjC,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,MAAM,CAA0C;IACxD,OAAO,CAAC,wBAAwB,CAAuB;IACvD,OAAO,CAAC,qBAAqB,CAAqC;IAClE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,IAAI,GAAG,KAAK,GAAG,MAAM,CAAC,CAAC;gBAE5D,MAAM,EAAE,kBAAkB;
|
|
1
|
+
{"version":3,"file":"Orchestrator.d.ts","sourceRoot":"","sources":["../../src/orchestrator/Orchestrator.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Orchestrator.js","sources":["../../src/orchestrator/Orchestrator.ts"],"sourcesContent":["import { EventBus } from '../event/EventBus';\nimport { WorkerPool } from '../worker/WorkerPool';\nimport { applyPatch as applyModelPatch } from '../model/patch';\nimport { ResourceLoader } from '../stages/load/ResourceLoader';\nimport { CacheManager } from '../cache/CacheManager';\nimport { ConfigLoader } from '../config/ConfigLoader';\nimport type { IOrchestrator, OrchestratorConfig, RenderFrameOptions } from './types';\nimport { WorkerType } from '../worker/types';\nimport { CompositionModel, CompositionPatch, Resource, TimeUs, RcFrame, Clip } from '../model';\nimport { hasResourceId } from '../model/types';\nimport { MeframeEvent, type EventPayloadMap } from '../event/events';\nimport { CompositionPlanner } from './CompositionPlanner';\nimport { GlobalAudioSession } from './GlobalAudioSession';\nimport { MuxManager } from '../stages/mux/MuxManager';\nimport { ExportOptions } from '../types';\nimport { OnDemandVideoSession } from './OnDemandVideoSession';\nimport { ExportScheduler } from './ExportScheduler';\n\nexport class Orchestrator implements IOrchestrator {\n workers: WorkerPool;\n eventBus: EventBus<EventPayloadMap>;\n compositionModel: CompositionModel | null = null;\n resourceLoader: ResourceLoader;\n cacheManager: CacheManager;\n planner: CompositionPlanner;\n audioSession: GlobalAudioSession;\n muxManager: MuxManager;\n exportScheduler: ExportScheduler;\n\n private isInitialized = false;\n private config = ConfigLoader.getInstance().getConfig();\n private ensureCacheDebounceTimer: number | null = null;\n private activeOnDemandSession: OnDemandVideoSession | null = null;\n readonly events: Pick<EventBus<EventPayloadMap>, 'on' | 'off' | 'once'>;\n\n constructor(config: OrchestratorConfig) {\n // Use provided eventBus or create a new one\n this.eventBus = config.eventBus || new EventBus<EventPayloadMap>();\n this.events = this.eventBus.asReadonly();\n\n // Initialize config first\n this.config = ConfigLoader.getInstance().getConfig();\n\n const workerConfigs = this.buildWorkerConfigs();\n\n // Initialize WorkerPool with worker path from config\n this.workers = new WorkerPool({\n eventBus: this.eventBus,\n workerConfigs,\n workerPath: config.workerPath,\n workerExtension: config.workerExtension,\n });\n\n const maxMemoryMB = config.cacheConfig?.l1Size || this.config.cache?.l1?.maxMemoryMB || 1024;\n const maxGOPs = this.config.decode?.video?.maxGOPs || 4;\n\n this.cacheManager = new CacheManager(\n {\n l1: {\n maxMemoryMB,\n maxGOPs,\n },\n resource: {\n projectId: config.projectId || this.config.global.projectId || 'default',\n },\n },\n this.eventBus\n );\n\n this.resourceLoader = new ResourceLoader({\n cacheManager: this.cacheManager,\n workerPool: this.workers,\n eventBus: this.eventBus,\n config: {\n maxConcurrent: this.config.load.maxConcurrent,\n preloadConcurrency: 2, // Fixed preload concurrency for idle background loading\n },\n onStateChange: (resourceId, state) => this.handleResourceStateChange(resourceId, state),\n });\n\n this.planner = new CompositionPlanner();\n\n this.audioSession = new GlobalAudioSession({\n cacheManager: this.cacheManager,\n workerPool: this.workers,\n resourceLoader: this.resourceLoader,\n eventBus: this.eventBus,\n buildWorkerConfigs: () => this.buildWorkerConfigs(),\n });\n\n this.muxManager = new MuxManager(\n this.cacheManager,\n this.audioSession,\n this.config.encode.audio as AudioEncoderConfig\n );\n\n this.exportScheduler = new ExportScheduler({\n workerPool: this.workers,\n planner: this.planner,\n cacheManager: this.cacheManager,\n resourceLoader: this.resourceLoader,\n muxManager: this.muxManager,\n audioSession: this.audioSession,\n workerConfigsProvider: () => this.buildWorkerConfigs(),\n eventBus: this.eventBus,\n });\n\n this.setupResourceFirstFrameHandler();\n this.setupPreloadHandlers();\n }\n\n private setupResourceFirstFrameHandler(): void {\n this.eventBus.on(MeframeEvent.ResourceFirstFrameReady, async (payload) => {\n const { resourceId, clipId, index, chunks } = payload;\n\n if (!this.compositionModel) return;\n\n // Find the specific clip\n const clip = this.compositionModel.findClip(clipId);\n if (!clip || !clip.trackId) return;\n\n // Only decode first frame for clips that start at composition time 0\n // (these clips need the resource's first frame as cover)\n if (clip.startUs === 0) {\n const fps = this.compositionModel.fps ?? 30;\n await OnDemandVideoSession.decodeAndCacheFirstFrame(\n resourceId,\n chunks,\n index,\n clip,\n this.cacheManager,\n fps\n );\n }\n });\n }\n\n private setupPreloadHandlers(): void {\n // Stop preloading when playback starts\n this.eventBus.on(MeframeEvent.PlaybackPlay, () => {\n this.resourceLoader.setPreloadingEnabled(false);\n });\n\n // Enable preloading when playback pauses/stops\n this.eventBus.on(MeframeEvent.PlaybackPause, () => {\n this.resourceLoader.setPreloadingEnabled(true);\n });\n\n this.eventBus.on(MeframeEvent.PlaybackStop, () => {\n this.resourceLoader.setPreloadingEnabled(true);\n });\n\n // Note: ModelSet and PatchApplied are handled internally in ResourceLoader via handleModelSet\n // and direct calls isn't needed as ResourceLoader listens to updateResourceState via onStateChange?\n // Wait, ResourceLoader handles ModelSet via eventHandlers.\n }\n\n async initialize(): Promise<void> {\n if (this.isInitialized) return;\n\n await this.cacheManager.init();\n\n this.isInitialized = true;\n }\n\n // Event methods - forward to eventBus\n on<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.on(event, handler);\n }\n\n off<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.off(event, handler);\n }\n\n once<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.once(event, handler);\n }\n\n cancelActiveDecoding(): void {\n if (this.activeOnDemandSession) {\n void this.activeOnDemandSession.dispose();\n this.activeOnDemandSession = null;\n }\n }\n\n /**\n * Decode and render nearest keyframe for fast seek preview\n * Returns the keyframe timestamp if successfully decoded, null otherwise\n */\n async tryRenderKeyframe(timeUs: TimeUs): Promise<TimeUs | null> {\n if (!this.compositionModel) return null;\n\n const clip = this.compositionModel.getClipsAtTime(timeUs, this.compositionModel.mainTrackId)[0];\n if (!clip || !hasResourceId(clip)) return null;\n\n // Use resource-relative time (same as frame.timestamp from decoder)\n const resourceTimeUs = timeUs - clip.startUs + (clip.trimStartUs ?? 0);\n const resourceId = clip.resourceId;\n\n // Check if keyframe is already in L1 cache\n const keyframeSample = this.cacheManager.mp4IndexCache.getNearestKeyframe(\n resourceId,\n resourceTimeUs\n );\n\n if (!keyframeSample) return null;\n\n const cachedKeyframe = this.cacheManager.getFrame(keyframeSample.timestamp, clip.id);\n if (cachedKeyframe) {\n return keyframeSample.timestamp;\n }\n\n // Resource must be ready to decode keyframe\n const resource = this.compositionModel.getResource(resourceId);\n if (resource?.state !== 'ready') return null;\n\n // Create temporary session to decode keyframe\n const session = await OnDemandVideoSession.create({\n clipId: clip.id,\n resourceId,\n targetTimeUs: resourceTimeUs,\n globalTimeUs: timeUs,\n resourceLoader: this.resourceLoader,\n mp4IndexCache: this.cacheManager.mp4IndexCache,\n cacheManager: this.cacheManager,\n compositionModel: this.compositionModel,\n fps: this.compositionModel.fps ?? 30,\n });\n\n try {\n const keyframeTimeUs = await session.decodeKeyframe(resourceTimeUs);\n return keyframeTimeUs;\n } catch (error) {\n console.warn('[Orchestrator] Fast keyframe decode failed:', error);\n return null;\n } finally {\n await session.dispose();\n }\n }\n\n async setCompositionModel(model: CompositionModel): Promise<void> {\n this.compositionModel = model;\n this.cacheManager.clear();\n this.planner.setModel(model);\n // ensure the cover resource is preloaded before audio is activated\n await this.resourceLoader.setModel(model);\n this.audioSession.setModel(model);\n\n this.eventBus.emit(MeframeEvent.ModelSet, model);\n\n this.eventBus.emit(MeframeEvent.CompositionUpdated, {\n trackCount: model.tracks.length,\n clipCount: model.tracks.reduce((acc: number, track: any) => acc + track.clips.length, 0),\n durationUs: model.durationUs,\n });\n }\n\n async applyPatch(patch: CompositionPatch): Promise<void> {\n if (!this.compositionModel) {\n throw new Error('No composition model set');\n }\n\n // Apply patch and get affected clip IDs (simplified for 2-Clip strategy)\n // Note: addTrack/removeTrack already call buildIndexes() in patch.ts\n const affectedClipIds = applyModelPatch(this.compositionModel, patch);\n this.planner.applyPatch(patch, affectedClipIds);\n this.eventBus.emit(MeframeEvent.PatchApplied, {\n operations: patch.operations.length,\n affectedClips: Array.from(affectedClipIds),\n });\n\n // Process clip updates\n for (const clipId of affectedClipIds) {\n this.cacheManager.invalidateClip(clipId);\n }\n\n // Reactivate updated audio clips\n const reactivatedAudioClips: string[] = [];\n const reactivatedVideoClips: string[] = [];\n for (const clipId of affectedClipIds) {\n const clip = this.compositionModel.findClip(clipId);\n if (clip?.trackKind === 'audio') {\n await this.audioSession.deactivateClip(clipId);\n reactivatedAudioClips.push(clipId);\n } else if (clip?.trackKind === 'video') {\n reactivatedVideoClips.push(clipId);\n }\n }\n\n // Activate all audio clips (including reactivated ones)\n await this.audioSession.activateAllAudioClips();\n\n // Note: No need to restart per-clip playback in new architecture\n // scheduleAudio() uses OfflineAudioMixer which automatically includes all active clips\n }\n\n private handleResourceStateChange(resourceId: string, state: Resource['state']): void {\n if (!this.compositionModel) {\n return;\n }\n\n this.compositionModel.updateResourceState(resourceId, state ?? 'pending');\n\n if (state !== 'ready') {\n return;\n }\n\n // For preview, simple cache invalidation or triggering re-render might be enough\n // if we were caching instructions. But PlaybackController pulls instructions every frame.\n // So just updating Model state is enough.\n }\n\n async getFrame(timeUs: TimeUs, options?: RenderFrameOptions): Promise<RcFrame | null> {\n const signal = options?.signal;\n const preheat = options?.preheat ?? false;\n\n if (!this.compositionModel) {\n throw new Error('No composition model set');\n }\n\n const clip = this.compositionModel.getClipsAtTime(timeUs, this.compositionModel.mainTrackId)[0];\n if (!clip) {\n return null;\n }\n\n const trimStartUs = clip.trimStartUs ?? 0;\n\n // Calculate resource-relative time (same as frame.timestamp from decoder)\n // resourceTimeUs = clipRelativeTime + trimStartUs\n let resourceTimeUs = options?.relativeTimeUs\n ? options.relativeTimeUs + trimStartUs\n : timeUs - clip.startUs + trimStartUs;\n\n // Clamp to valid range: [trimStartUs, trimStartUs + durationUs)\n resourceTimeUs = Math.min(resourceTimeUs, trimStartUs + clip.durationUs - 1);\n\n // 1. Check L1 window cache\n // Note: preheat mode skips cache check to force decoding the entire window\n // Why: Video cache is frame-level (point query), not window-level (range query)\n // A single cached frame doesn't guarantee the entire window is cached\n // Without preheat flag, preheating would return early on first frame hit\n if (!preheat) {\n const cachedFrame = this.cacheManager.getFrame(resourceTimeUs, clip.id);\n if (cachedFrame) {\n this.eventBus.emit(MeframeEvent.CacheHit, {\n timeUs,\n level: 'L1',\n key: `${clip.id}-${resourceTimeUs}`,\n });\n return cachedFrame;\n }\n this.eventBus.emit(MeframeEvent.CacheMiss, {\n timeUs,\n level: 'L1',\n key: `${clip.id}-${resourceTimeUs}`,\n });\n }\n\n if (signal?.aborted) {\n return null;\n }\n\n // 2. Try decode from OPFS resource (on-demand path)\n const resourceFrame = await this.decodeFromResource(clip, resourceTimeUs, timeUs, options);\n return resourceFrame;\n }\n\n /**\n * Compose frame from OPFS resource (on-demand decoding)\n * This is the new path for long clips with window caching\n *\n * @param clip - The clip to decode\n * @param resourceTimeUs - Time in resource-relative coordinates (same as frame.timestamp)\n * @param globalTimeUs - Time in composition timeline\n */\n private async decodeFromResource(\n clip: Clip,\n resourceTimeUs: TimeUs,\n globalTimeUs: TimeUs,\n options?: RenderFrameOptions\n ): Promise<RcFrame | null> {\n if (!hasResourceId(clip)) return null;\n\n const resourceId = clip.resourceId;\n const trimStartUs = clip.trimStartUs ?? 0;\n\n // Check resource state\n const resource = this.compositionModel?.getResource(resourceId);\n const isReady = resource?.state === 'ready';\n\n const fetchOptions = {\n isPreload: false,\n clipId: clip.id,\n trackId: clip.trackId,\n };\n\n // In immediate mode, if not ready, trigger fetch in background and return null\n if (options?.immediate && !isReady) {\n // Fire and forget fetch to start loading\n this.resourceLoader.load(resourceId, fetchOptions);\n return null;\n }\n\n // Normal mode: wait for download\n await this.resourceLoader.load(resourceId, fetchOptions);\n\n this.cancelActiveDecoding();\n\n // Create temporary on-demand video session\n const session = await OnDemandVideoSession.create({\n clipId: clip.id,\n resourceId,\n targetTimeUs: resourceTimeUs,\n globalTimeUs,\n resourceLoader: this.resourceLoader,\n mp4IndexCache: this.cacheManager.mp4IndexCache,\n cacheManager: this.cacheManager,\n compositionModel: this.compositionModel!,\n fps: this.compositionModel?.fps ?? 30,\n });\n\n this.activeOnDemandSession = session;\n\n try {\n // Decode window: from target position to target + 3s\n // Window bounds are in resource time: [trimStartUs, trimStartUs + durationUs)\n const DECODE_WINDOW_SIZE = 3_000_000;\n\n const windowStart = resourceTimeUs;\n const windowEnd = Math.min(\n trimStartUs + clip.durationUs,\n resourceTimeUs + DECODE_WINDOW_SIZE\n );\n\n await session.decodeWindow(windowStart, windowEnd);\n // Return target frame from L1 cache\n return this.cacheManager.getFrame(resourceTimeUs, clip.id);\n } catch (error) {\n if (session.isDisposed) {\n return null;\n }\n console.error('[Orchestrator] Error composing from resource:', error);\n return null;\n } finally {\n if (this.activeOnDemandSession === session) {\n this.activeOnDemandSession = null;\n }\n await session.dispose();\n }\n }\n\n /**\n * Wait for clip cache to be ready for playback\n * Returns true if minimum cache is ready, false if timeout\n */\n async waitForClipReady(\n timeUs: TimeUs,\n options?: { minFrameCount?: number; timeoutMs?: number }\n ): Promise<boolean> {\n if (!this.compositionModel) {\n return false;\n }\n\n const clips = this.compositionModel.getClipsAtTime(timeUs, this.compositionModel.mainTrackId);\n if (clips.length === 0) {\n return true;\n }\n\n const currentClip = clips[0];\n if (!currentClip) {\n return true;\n }\n\n return this.cacheManager.waitForClipReady(currentClip.id, {\n minFrameCount: options?.minFrameCount ?? 5,\n timeoutMs: options?.timeoutMs ?? 5_000,\n });\n }\n\n async dispose(): Promise<void> {\n if (this.ensureCacheDebounceTimer !== null) {\n clearTimeout(this.ensureCacheDebounceTimer);\n this.ensureCacheDebounceTimer = null;\n }\n\n this.resourceLoader.dispose();\n await this.cacheManager.clear();\n\n this.workers.terminateAll();\n this.compositionModel = null;\n this.eventBus.dispose();\n }\n\n private buildWorkerConfigs(): Record<WorkerType, any> {\n const config = this.config;\n const defaultCanvasWidth = config.global.defaultCanvasWidth;\n const defaultCanvasHeight = config.global.defaultCanvasHeight;\n const defaultFps = config.global.defaultFps;\n\n const targetFps = this.compositionModel?.fps ?? defaultFps;\n\n return {\n // videoDemux: { // DEPRECATED: Removed - replaced by IndexedVideoSource\n // highWaterMark: config.demux.backpressure.highWaterMark,\n // },\n audioDemux: {\n highWaterMark: config.demux.backpressure.highWaterMark,\n },\n // videoDecode: config.decode.video, // DEPRECATED: Removed - replaced by OnDemandVideoSession\n audioDecode: config.decode.audio,\n videoCompose: {\n width: defaultCanvasWidth,\n height: defaultCanvasHeight,\n fps: targetFps,\n backgroundColor: '#000000',\n enableSmoothing: true,\n enableHardwareAcceleration: true,\n fonts: config.global.fonts,\n },\n audioCompose: {\n enableDucking: config.compose.audio.enableDucking,\n },\n videoEncode: {\n // Main Profile Level 4.1 - better compression efficiency for social media\n codec: 'avc1.4D0029',\n width: defaultCanvasWidth,\n height: defaultCanvasHeight,\n bitrate: config.encode.video.bitrateKbps\n ? config.encode.video.bitrateKbps * 1000\n : this.calculateDefaultBitrate(defaultCanvasWidth, defaultCanvasHeight),\n framerate: targetFps,\n latencyMode: 'quality',\n bitrateMode: 'variable',\n hardwareAcceleration: 'no-preference',\n // Default 1 second keyframe interval for better social media compatibility\n keyFrameInterval: config.encode.video.keyIntervalS\n ? Math.round(config.encode.video.keyIntervalS * targetFps)\n : targetFps,\n ...config.encode.video,\n },\n };\n }\n\n /**\n * Calculate default video bitrate based on resolution\n * Optimized for social media platforms (YouTube, TikTok, WeChat, etc.)\n */\n private calculateDefaultBitrate(width: number, height: number): number {\n const pixels = width * height;\n\n // Bitrate recommendations for H.264 Main Profile VBR:\n // - 720p (921,600 px): 5 Mbps\n // - 1080p (2,073,600 px): 8 Mbps\n // - 4K (8,294,400 px): 25 Mbps\n if (pixels <= 921_600) {\n // 720p and below\n return 5_000_000;\n } else if (pixels <= 2_073_600) {\n // 1080p\n return 8_000_000;\n } else if (pixels <= 3_686_400) {\n // 1440p (2K)\n return 16_000_000;\n } else {\n // 4K and above\n return 25_000_000;\n }\n }\n\n async export(model: CompositionModel, options: ExportOptions): Promise<Blob> {\n return this.exportScheduler.execute(model, options);\n }\n\n /**\n * Preheat a specific clip's window range\n * Used by PlaybackController for cross-clip window preheating\n *\n * @param clipId - Clip identifier\n * @param clipRelativeStart - Start time relative to clip (microseconds), 0 = clip start\n * @param clipRelativeEnd - End time relative to clip (microseconds)\n * @param globalTimeUs - Global timeline position (for globalTimeUs in cache)\n */\n async preheatClipWindow(\n clipId: string,\n clipRelativeStart: TimeUs,\n clipRelativeEnd: TimeUs,\n globalTimeUs: TimeUs\n ): Promise<void> {\n if (!this.compositionModel) return;\n\n const clip = this.compositionModel.findClip(clipId);\n if (!clip || !hasResourceId(clip)) return;\n\n const resourceId = clip.resourceId;\n const trimStartUs = clip.trimStartUs ?? 0;\n\n // Convert clip-relative time to resource time\n const resourceStart = clipRelativeStart + trimStartUs;\n const resourceEnd = clipRelativeEnd + trimStartUs;\n\n // Ensure resource is downloaded\n await this.resourceLoader.load(resourceId, {\n isPreload: false,\n clipId: clip.id,\n trackId: clip.trackId,\n });\n\n // Create temporary on-demand session for this window\n const session = await OnDemandVideoSession.create({\n clipId: clip.id,\n resourceId,\n targetTimeUs: resourceStart,\n globalTimeUs,\n resourceLoader: this.resourceLoader,\n mp4IndexCache: this.cacheManager.mp4IndexCache,\n cacheManager: this.cacheManager,\n compositionModel: this.compositionModel,\n fps: this.compositionModel.fps ?? 30,\n });\n\n try {\n // Decode the entire window range for this clip (using resource time)\n await session.decodeWindow(resourceStart, resourceEnd);\n } catch (error) {\n console.warn(`[Orchestrator] Preheat clip ${clipId} window failed:`, error);\n // Non-critical, don't throw\n } finally {\n await session.dispose();\n }\n }\n\n /**\n * Get render state for real-time composition\n * Returns layers ready for VideoComposer\n */\n async getRenderState(\n timeUs: TimeUs,\n options?: RenderFrameOptions\n ): Promise<{ layers: any[]; transition?: any } | null> {\n if (!this.compositionModel) {\n return null;\n }\n\n // Ensure frame/resource is ready (this populates L1 if needed)\n const frame = await this.getFrame(timeUs, options);\n\n // If immediate mode and no frame, return null to trigger buffering\n if (!frame) {\n return null;\n }\n\n const clip = this.compositionModel.getClipsAtTime(timeUs, this.compositionModel.mainTrackId)[0];\n if (!clip) {\n return null;\n }\n\n const clipRelativeTimeUs = timeUs - clip.startUs;\n const resourceTimeUs = clipRelativeTimeUs + (clip.trimStartUs ?? 0);\n\n // Get instructions from planner\n const instructions = this.planner.getInstructions(clip.id);\n if (!instructions) {\n return null;\n }\n\n // Build layers array\n const layers: any[] = [];\n\n // 1. Filter active layers at this timestamp (uses clip-relative time for activeRanges)\n const activeLayers = instructions.layers.filter((layer: any) => {\n if (!layer.payload.attachmentId) {\n // Main track layer is always active\n return true;\n }\n if (layer.status !== 'ready') {\n return false;\n }\n // Check if layer is active at current timestamp\n return layer.activeRanges.some(\n (range: any) => clipRelativeTimeUs >= range.startUs && clipRelativeTimeUs < range.endUs\n );\n });\n\n // 2. Materialize layers\n for (const layerPlan of activeLayers) {\n const layer = await this.materializeLayer(layerPlan, clip, resourceTimeUs, timeUs);\n if (layer) {\n layers.push(layer);\n }\n }\n\n return { layers };\n }\n\n /**\n * Materialize a serialized layer plan into concrete Layer\n */\n private async materializeLayer(\n layerPlan: any,\n clip: Clip,\n resourceTimeUs: TimeUs,\n globalTimeUs: TimeUs\n ): Promise<any | null> {\n const baseLayer: any = {\n id: layerPlan.layerId,\n type: layerPlan.type,\n zIndex: layerPlan.zIndex ?? 0,\n visible: true,\n opacity: layerPlan.opacity ?? 1,\n };\n\n // Video layer - fetch raw VideoFrame from L1 (RcFrame wrapper)\n if (layerPlan.type === 'video' && !layerPlan.payload.attachmentId) {\n const rcFrame = this.cacheManager.getFrame(resourceTimeUs, clip.id);\n if (!rcFrame) {\n console.warn('[Orchestrator] Video frame not found in L1:', clip.id, resourceTimeUs);\n return null;\n }\n\n return {\n ...baseLayer,\n type: 'video',\n rcFrame: rcFrame,\n };\n }\n\n // Text layer\n if (layerPlan.type === 'text') {\n const payload = layerPlan.payload;\n return {\n ...baseLayer,\n type: 'text',\n text: payload.text,\n localeCode: payload.localeCode,\n fontConfig: payload.fontConfig,\n animation: payload.animation,\n wordTimings: payload.wordTimings,\n letterCase: payload.letterCase,\n };\n }\n\n // Image layer\n if (layerPlan.type === 'image') {\n const payload = layerPlan.payload;\n const resource = this.compositionModel?.getResource(payload.resourceId);\n if (!resource) {\n return null;\n }\n\n const source = await this.resourceLoader.loadImage(resource);\n const imageLayer: any = {\n ...baseLayer,\n type: 'image',\n source,\n attachmentId: payload.attachmentId,\n };\n\n if (payload.targetWidth !== undefined) {\n imageLayer.targetWidth = payload.targetWidth;\n }\n if (payload.targetHeight !== undefined) {\n imageLayer.targetHeight = payload.targetHeight;\n }\n\n // Handle animation (overlays)\n if (payload.animation) {\n const { position, keyframes, overlayClipStartUs } = payload.animation;\n\n // Calculate time relative to overlay clip start\n const relativeTimeUs = globalTimeUs - overlayClipStartUs;\n\n // If outside keyframe range, hide\n if (relativeTimeUs < 0 || relativeTimeUs > keyframes[keyframes.length - 1].time) {\n return null; // Not visible at this time\n }\n\n const rotationRad = 0; // TODO: interpolate from keyframes\n\n imageLayer.transform = {\n x: position.x,\n y: position.y,\n scaleX: 1,\n scaleY: 1,\n rotation: rotationRad,\n anchorX: 0.5,\n anchorY: 0.5,\n };\n }\n\n return imageLayer;\n }\n\n return baseLayer;\n }\n}\n"],"names":["applyModelPatch"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Orchestrator.js","sources":["../../src/orchestrator/Orchestrator.ts"],"sourcesContent":["import { EventBus } from '../event/EventBus';\nimport { WorkerPool } from '../worker/WorkerPool';\nimport { applyPatch as applyModelPatch } from '../model/patch';\nimport { ResourceLoader } from '../stages/load/ResourceLoader';\nimport { CacheManager } from '../cache/CacheManager';\nimport { ConfigLoader } from '../config/ConfigLoader';\nimport type { IOrchestrator, OrchestratorConfig, RenderFrameOptions } from './types';\nimport { WorkerType } from '../worker/types';\nimport { CompositionModel, CompositionPatch, Resource, TimeUs, RcFrame, Clip } from '../model';\nimport { hasResourceId } from '../model/types';\nimport { MeframeEvent, type EventPayloadMap } from '../event/events';\nimport { CompositionPlanner } from './CompositionPlanner';\nimport { GlobalAudioSession } from './GlobalAudioSession';\nimport { MuxManager } from '../stages/mux/MuxManager';\nimport { ExportOptions } from '../types';\nimport { OnDemandVideoSession } from './OnDemandVideoSession';\nimport { ExportScheduler } from './ExportScheduler';\n\nexport class Orchestrator implements IOrchestrator {\n workers: WorkerPool;\n eventBus: EventBus<EventPayloadMap>;\n compositionModel: CompositionModel | null = null;\n resourceLoader: ResourceLoader;\n cacheManager: CacheManager;\n planner: CompositionPlanner;\n audioSession: GlobalAudioSession;\n muxManager: MuxManager;\n exportScheduler: ExportScheduler;\n\n private isInitialized = false;\n private config = ConfigLoader.getInstance().getConfig();\n private ensureCacheDebounceTimer: number | null = null;\n private activeOnDemandSession: OnDemandVideoSession | null = null;\n readonly events: Pick<EventBus<EventPayloadMap>, 'on' | 'off' | 'once'>;\n\n constructor(config: OrchestratorConfig) {\n // Use provided eventBus or create a new one\n this.eventBus = config.eventBus || new EventBus<EventPayloadMap>();\n this.events = this.eventBus.asReadonly();\n\n // Initialize config first\n this.config = ConfigLoader.getInstance().getConfig();\n\n const workerConfigs = this.buildWorkerConfigs();\n\n // Initialize WorkerPool with worker path from config\n this.workers = new WorkerPool({\n eventBus: this.eventBus,\n workerConfigs,\n workerPath: config.workerPath,\n workerExtension: config.workerExtension,\n });\n\n const maxMemoryMB = config.cacheConfig?.l1Size || this.config.cache?.l1?.maxMemoryMB || 1024;\n const maxGOPs = this.config.decode?.video?.maxGOPs || 4;\n\n this.cacheManager = new CacheManager(\n {\n l1: {\n maxMemoryMB,\n maxGOPs,\n },\n resource: {\n projectId: config.projectId || this.config.global.projectId || 'default',\n },\n },\n this.eventBus\n );\n\n this.resourceLoader = new ResourceLoader({\n cacheManager: this.cacheManager,\n eventBus: this.eventBus,\n config: {\n maxConcurrent: this.config.load.maxConcurrent,\n preloadConcurrency: 2, // Fixed preload concurrency for idle background loading\n },\n onStateChange: (resourceId, state) => this.handleResourceStateChange(resourceId, state),\n });\n\n this.planner = new CompositionPlanner();\n\n this.audioSession = new GlobalAudioSession({\n cacheManager: this.cacheManager,\n workerPool: this.workers,\n resourceLoader: this.resourceLoader,\n eventBus: this.eventBus,\n buildWorkerConfigs: () => this.buildWorkerConfigs(),\n });\n\n this.muxManager = new MuxManager(\n this.cacheManager,\n this.audioSession,\n this.config.encode.audio as AudioEncoderConfig\n );\n\n this.exportScheduler = new ExportScheduler({\n workerPool: this.workers,\n planner: this.planner,\n cacheManager: this.cacheManager,\n resourceLoader: this.resourceLoader,\n muxManager: this.muxManager,\n audioSession: this.audioSession,\n workerConfigsProvider: () => this.buildWorkerConfigs(),\n eventBus: this.eventBus,\n });\n\n this.setupResourceFirstFrameHandler();\n this.setupPreloadHandlers();\n }\n\n private setupResourceFirstFrameHandler(): void {\n this.eventBus.on(MeframeEvent.ResourceFirstFrameReady, async (payload) => {\n const { resourceId, clipId, index, chunks } = payload;\n\n if (!this.compositionModel) return;\n\n // Find the specific clip\n const clip = this.compositionModel.findClip(clipId);\n if (!clip || !clip.trackId) return;\n\n // Only decode first frame for clips that start at composition time 0\n // (these clips need the resource's first frame as cover)\n if (clip.startUs === 0) {\n const fps = this.compositionModel.fps ?? 30;\n await OnDemandVideoSession.decodeAndCacheFirstFrame(\n resourceId,\n chunks,\n index,\n clip,\n this.cacheManager,\n fps\n );\n }\n });\n }\n\n private setupPreloadHandlers(): void {\n // Stop preloading when playback starts\n this.eventBus.on(MeframeEvent.PlaybackPlay, () => {\n this.resourceLoader.setPreloadingEnabled(false);\n });\n\n // Enable preloading when playback pauses/stops\n this.eventBus.on(MeframeEvent.PlaybackPause, () => {\n this.resourceLoader.setPreloadingEnabled(true);\n });\n\n this.eventBus.on(MeframeEvent.PlaybackStop, () => {\n this.resourceLoader.setPreloadingEnabled(true);\n });\n\n // Note: ModelSet and PatchApplied are handled internally in ResourceLoader via handleModelSet\n // and direct calls isn't needed as ResourceLoader listens to updateResourceState via onStateChange?\n // Wait, ResourceLoader handles ModelSet via eventHandlers.\n }\n\n async initialize(): Promise<void> {\n if (this.isInitialized) return;\n\n await this.cacheManager.init();\n\n this.isInitialized = true;\n }\n\n // Event methods - forward to eventBus\n on<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.on(event, handler);\n }\n\n off<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.off(event, handler);\n }\n\n once<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.once(event, handler);\n }\n\n cancelActiveDecoding(): void {\n if (this.activeOnDemandSession) {\n void this.activeOnDemandSession.dispose();\n this.activeOnDemandSession = null;\n }\n }\n\n /**\n * Decode and render nearest keyframe for fast seek preview\n * Returns the keyframe timestamp if successfully decoded, null otherwise\n */\n async tryRenderKeyframe(timeUs: TimeUs): Promise<TimeUs | null> {\n if (!this.compositionModel) return null;\n\n const clip = this.compositionModel.getClipsAtTime(timeUs, this.compositionModel.mainTrackId)[0];\n if (!clip || !hasResourceId(clip)) return null;\n\n // Use resource-relative time (same as frame.timestamp from decoder)\n const resourceTimeUs = timeUs - clip.startUs + (clip.trimStartUs ?? 0);\n const resourceId = clip.resourceId;\n\n // Check if keyframe is already in L1 cache\n const keyframeSample = this.cacheManager.mp4IndexCache.getNearestKeyframe(\n resourceId,\n resourceTimeUs\n );\n\n if (!keyframeSample) return null;\n\n const cachedKeyframe = this.cacheManager.getFrame(keyframeSample.timestamp, clip.id);\n if (cachedKeyframe) {\n return keyframeSample.timestamp;\n }\n\n // Resource must be ready to decode keyframe\n const resource = this.compositionModel.getResource(resourceId);\n if (resource?.state !== 'ready') return null;\n\n // Create temporary session to decode keyframe\n const session = await OnDemandVideoSession.create({\n clipId: clip.id,\n resourceId,\n targetTimeUs: resourceTimeUs,\n globalTimeUs: timeUs,\n resourceLoader: this.resourceLoader,\n mp4IndexCache: this.cacheManager.mp4IndexCache,\n cacheManager: this.cacheManager,\n compositionModel: this.compositionModel,\n fps: this.compositionModel.fps ?? 30,\n });\n\n try {\n const keyframeTimeUs = await session.decodeKeyframe(resourceTimeUs);\n return keyframeTimeUs;\n } catch (error) {\n console.warn('[Orchestrator] Fast keyframe decode failed:', error);\n return null;\n } finally {\n await session.dispose();\n }\n }\n\n async setCompositionModel(model: CompositionModel): Promise<void> {\n this.compositionModel = model;\n this.cacheManager.clear();\n this.planner.setModel(model);\n // ensure the cover resource is preloaded before audio is activated\n await this.resourceLoader.setModel(model);\n this.audioSession.setModel(model);\n\n this.eventBus.emit(MeframeEvent.ModelSet, model);\n\n this.eventBus.emit(MeframeEvent.CompositionUpdated, {\n trackCount: model.tracks.length,\n clipCount: model.tracks.reduce((acc: number, track: any) => acc + track.clips.length, 0),\n durationUs: model.durationUs,\n });\n }\n\n async applyPatch(patch: CompositionPatch): Promise<void> {\n if (!this.compositionModel) {\n throw new Error('No composition model set');\n }\n\n // Apply patch and get affected clip IDs (simplified for 2-Clip strategy)\n // Note: addTrack/removeTrack already call buildIndexes() in patch.ts\n const affectedClipIds = applyModelPatch(this.compositionModel, patch);\n this.planner.applyPatch(patch, affectedClipIds);\n this.eventBus.emit(MeframeEvent.PatchApplied, {\n operations: patch.operations.length,\n affectedClips: Array.from(affectedClipIds),\n });\n\n // Process clip updates\n for (const clipId of affectedClipIds) {\n this.cacheManager.invalidateClip(clipId);\n }\n\n // Reactivate updated audio clips\n const reactivatedAudioClips: string[] = [];\n const reactivatedVideoClips: string[] = [];\n for (const clipId of affectedClipIds) {\n const clip = this.compositionModel.findClip(clipId);\n if (clip?.trackKind === 'audio') {\n await this.audioSession.deactivateClip(clipId);\n reactivatedAudioClips.push(clipId);\n } else if (clip?.trackKind === 'video') {\n reactivatedVideoClips.push(clipId);\n }\n }\n\n // Activate all audio clips (including reactivated ones)\n await this.audioSession.activateAllAudioClips();\n\n // Note: No need to restart per-clip playback in new architecture\n // scheduleAudio() uses OfflineAudioMixer which automatically includes all active clips\n }\n\n private handleResourceStateChange(resourceId: string, state: Resource['state']): void {\n if (!this.compositionModel) {\n return;\n }\n\n this.compositionModel.updateResourceState(resourceId, state ?? 'pending');\n\n if (state !== 'ready') {\n return;\n }\n\n // For preview, simple cache invalidation or triggering re-render might be enough\n // if we were caching instructions. But PlaybackController pulls instructions every frame.\n // So just updating Model state is enough.\n }\n\n async getFrame(timeUs: TimeUs, options?: RenderFrameOptions): Promise<RcFrame | null> {\n const signal = options?.signal;\n const preheat = options?.preheat ?? false;\n\n if (!this.compositionModel) {\n throw new Error('No composition model set');\n }\n\n const clip = this.compositionModel.getClipsAtTime(timeUs, this.compositionModel.mainTrackId)[0];\n if (!clip) {\n return null;\n }\n\n const trimStartUs = clip.trimStartUs ?? 0;\n\n // Calculate resource-relative time (same as frame.timestamp from decoder)\n // resourceTimeUs = clipRelativeTime + trimStartUs\n let resourceTimeUs = options?.relativeTimeUs\n ? options.relativeTimeUs + trimStartUs\n : timeUs - clip.startUs + trimStartUs;\n\n // Clamp to valid range: [trimStartUs, trimStartUs + durationUs)\n resourceTimeUs = Math.min(resourceTimeUs, trimStartUs + clip.durationUs - 1);\n\n // 1. Check L1 window cache\n // Note: preheat mode skips cache check to force decoding the entire window\n // Why: Video cache is frame-level (point query), not window-level (range query)\n // A single cached frame doesn't guarantee the entire window is cached\n // Without preheat flag, preheating would return early on first frame hit\n if (!preheat) {\n const cachedFrame = this.cacheManager.getFrame(resourceTimeUs, clip.id);\n if (cachedFrame) {\n this.eventBus.emit(MeframeEvent.CacheHit, {\n timeUs,\n level: 'L1',\n key: `${clip.id}-${resourceTimeUs}`,\n });\n return cachedFrame;\n }\n this.eventBus.emit(MeframeEvent.CacheMiss, {\n timeUs,\n level: 'L1',\n key: `${clip.id}-${resourceTimeUs}`,\n });\n }\n\n if (signal?.aborted) {\n return null;\n }\n\n // 2. Try decode from OPFS resource (on-demand path)\n const resourceFrame = await this.decodeFromResource(clip, resourceTimeUs, timeUs, options);\n return resourceFrame;\n }\n\n /**\n * Compose frame from OPFS resource (on-demand decoding)\n * This is the new path for long clips with window caching\n *\n * @param clip - The clip to decode\n * @param resourceTimeUs - Time in resource-relative coordinates (same as frame.timestamp)\n * @param globalTimeUs - Time in composition timeline\n */\n private async decodeFromResource(\n clip: Clip,\n resourceTimeUs: TimeUs,\n globalTimeUs: TimeUs,\n options?: RenderFrameOptions\n ): Promise<RcFrame | null> {\n if (!hasResourceId(clip)) return null;\n\n const resourceId = clip.resourceId;\n const trimStartUs = clip.trimStartUs ?? 0;\n\n // Check resource state\n const resource = this.compositionModel?.getResource(resourceId);\n const isReady = resource?.state === 'ready';\n\n const fetchOptions = {\n isPreload: false,\n clipId: clip.id,\n trackId: clip.trackId,\n };\n\n // In immediate mode, if not ready, trigger fetch in background and return null\n if (options?.immediate && !isReady) {\n // Fire and forget fetch to start loading\n this.resourceLoader.load(resourceId, fetchOptions);\n return null;\n }\n\n // Normal mode: wait for download\n await this.resourceLoader.load(resourceId, fetchOptions);\n\n this.cancelActiveDecoding();\n\n // Create temporary on-demand video session\n const session = await OnDemandVideoSession.create({\n clipId: clip.id,\n resourceId,\n targetTimeUs: resourceTimeUs,\n globalTimeUs,\n resourceLoader: this.resourceLoader,\n mp4IndexCache: this.cacheManager.mp4IndexCache,\n cacheManager: this.cacheManager,\n compositionModel: this.compositionModel!,\n fps: this.compositionModel?.fps ?? 30,\n });\n\n this.activeOnDemandSession = session;\n\n try {\n // Decode window: from target position to target + 3s\n // Window bounds are in resource time: [trimStartUs, trimStartUs + durationUs)\n const DECODE_WINDOW_SIZE = 3_000_000;\n\n const windowStart = resourceTimeUs;\n const windowEnd = Math.min(\n trimStartUs + clip.durationUs,\n resourceTimeUs + DECODE_WINDOW_SIZE\n );\n\n await session.decodeWindow(windowStart, windowEnd);\n // Return target frame from L1 cache\n return this.cacheManager.getFrame(resourceTimeUs, clip.id);\n } catch (error) {\n if (session.isDisposed) {\n return null;\n }\n console.error('[Orchestrator] Error composing from resource:', error);\n return null;\n } finally {\n if (this.activeOnDemandSession === session) {\n this.activeOnDemandSession = null;\n }\n await session.dispose();\n }\n }\n\n /**\n * Wait for clip cache to be ready for playback\n * Returns true if minimum cache is ready, false if timeout\n */\n async waitForClipReady(\n timeUs: TimeUs,\n options?: { minFrameCount?: number; timeoutMs?: number }\n ): Promise<boolean> {\n if (!this.compositionModel) {\n return false;\n }\n\n const clips = this.compositionModel.getClipsAtTime(timeUs, this.compositionModel.mainTrackId);\n if (clips.length === 0) {\n return true;\n }\n\n const currentClip = clips[0];\n if (!currentClip) {\n return true;\n }\n\n return this.cacheManager.waitForClipReady(currentClip.id, {\n minFrameCount: options?.minFrameCount ?? 5,\n timeoutMs: options?.timeoutMs ?? 5_000,\n });\n }\n\n async dispose(): Promise<void> {\n if (this.ensureCacheDebounceTimer !== null) {\n clearTimeout(this.ensureCacheDebounceTimer);\n this.ensureCacheDebounceTimer = null;\n }\n\n this.resourceLoader.dispose();\n await this.cacheManager.clear();\n\n this.workers.terminateAll();\n this.compositionModel = null;\n this.eventBus.dispose();\n }\n\n private buildWorkerConfigs(): Record<WorkerType, any> {\n const config = this.config;\n const defaultCanvasWidth = config.global.defaultCanvasWidth;\n const defaultCanvasHeight = config.global.defaultCanvasHeight;\n const defaultFps = config.global.defaultFps;\n\n const targetFps = this.compositionModel?.fps ?? defaultFps;\n\n return {\n // videoDemux: { // DEPRECATED: Removed - replaced by IndexedVideoSource\n // highWaterMark: config.demux.backpressure.highWaterMark,\n // },\n audioDemux: {\n highWaterMark: config.demux.backpressure.highWaterMark,\n },\n // videoDecode: config.decode.video, // DEPRECATED: Removed - replaced by OnDemandVideoSession\n audioDecode: config.decode.audio,\n videoCompose: {\n width: defaultCanvasWidth,\n height: defaultCanvasHeight,\n fps: targetFps,\n backgroundColor: '#000000',\n enableSmoothing: true,\n enableHardwareAcceleration: true,\n fonts: config.global.fonts,\n },\n audioCompose: {\n enableDucking: config.compose.audio.enableDucking,\n },\n videoEncode: {\n // Main Profile Level 4.1 - better compression efficiency for social media\n codec: 'avc1.4D0029',\n width: defaultCanvasWidth,\n height: defaultCanvasHeight,\n bitrate: config.encode.video.bitrateKbps\n ? config.encode.video.bitrateKbps * 1000\n : this.calculateDefaultBitrate(defaultCanvasWidth, defaultCanvasHeight),\n framerate: targetFps,\n latencyMode: 'quality',\n bitrateMode: 'variable',\n hardwareAcceleration: 'no-preference',\n // Default 1 second keyframe interval for better social media compatibility\n keyFrameInterval: config.encode.video.keyIntervalS\n ? Math.round(config.encode.video.keyIntervalS * targetFps)\n : targetFps,\n ...config.encode.video,\n },\n };\n }\n\n /**\n * Calculate default video bitrate based on resolution\n * Optimized for social media platforms (YouTube, TikTok, WeChat, etc.)\n */\n private calculateDefaultBitrate(width: number, height: number): number {\n const pixels = width * height;\n\n // Bitrate recommendations for H.264 Main Profile VBR:\n // - 720p (921,600 px): 5 Mbps\n // - 1080p (2,073,600 px): 8 Mbps\n // - 4K (8,294,400 px): 25 Mbps\n if (pixels <= 921_600) {\n // 720p and below\n return 5_000_000;\n } else if (pixels <= 2_073_600) {\n // 1080p\n return 8_000_000;\n } else if (pixels <= 3_686_400) {\n // 1440p (2K)\n return 16_000_000;\n } else {\n // 4K and above\n return 25_000_000;\n }\n }\n\n async export(model: CompositionModel, options: ExportOptions): Promise<Blob> {\n return this.exportScheduler.execute(model, options);\n }\n\n /**\n * Preheat a specific clip's window range\n * Used by PlaybackController for cross-clip window preheating\n *\n * @param clipId - Clip identifier\n * @param clipRelativeStart - Start time relative to clip (microseconds), 0 = clip start\n * @param clipRelativeEnd - End time relative to clip (microseconds)\n * @param globalTimeUs - Global timeline position (for globalTimeUs in cache)\n */\n async preheatClipWindow(\n clipId: string,\n clipRelativeStart: TimeUs,\n clipRelativeEnd: TimeUs,\n globalTimeUs: TimeUs\n ): Promise<void> {\n if (!this.compositionModel) return;\n\n const clip = this.compositionModel.findClip(clipId);\n if (!clip || !hasResourceId(clip)) return;\n\n const resourceId = clip.resourceId;\n const trimStartUs = clip.trimStartUs ?? 0;\n\n // Convert clip-relative time to resource time\n const resourceStart = clipRelativeStart + trimStartUs;\n const resourceEnd = clipRelativeEnd + trimStartUs;\n\n // Ensure resource is downloaded\n await this.resourceLoader.load(resourceId, {\n isPreload: false,\n clipId: clip.id,\n trackId: clip.trackId,\n });\n\n // Create temporary on-demand session for this window\n const session = await OnDemandVideoSession.create({\n clipId: clip.id,\n resourceId,\n targetTimeUs: resourceStart,\n globalTimeUs,\n resourceLoader: this.resourceLoader,\n mp4IndexCache: this.cacheManager.mp4IndexCache,\n cacheManager: this.cacheManager,\n compositionModel: this.compositionModel,\n fps: this.compositionModel.fps ?? 30,\n });\n\n try {\n // Decode the entire window range for this clip (using resource time)\n await session.decodeWindow(resourceStart, resourceEnd);\n } catch (error) {\n console.warn(`[Orchestrator] Preheat clip ${clipId} window failed:`, error);\n // Non-critical, don't throw\n } finally {\n await session.dispose();\n }\n }\n\n /**\n * Get render state for real-time composition\n * Returns layers ready for VideoComposer\n */\n async getRenderState(\n timeUs: TimeUs,\n options?: RenderFrameOptions\n ): Promise<{ layers: any[]; transition?: any } | null> {\n if (!this.compositionModel) {\n return null;\n }\n\n // Ensure frame/resource is ready (this populates L1 if needed)\n const frame = await this.getFrame(timeUs, options);\n\n // If immediate mode and no frame, return null to trigger buffering\n if (!frame) {\n return null;\n }\n\n const clip = this.compositionModel.getClipsAtTime(timeUs, this.compositionModel.mainTrackId)[0];\n if (!clip) {\n return null;\n }\n\n const clipRelativeTimeUs = timeUs - clip.startUs;\n const resourceTimeUs = clipRelativeTimeUs + (clip.trimStartUs ?? 0);\n\n // Get instructions from planner\n const instructions = this.planner.getInstructions(clip.id);\n if (!instructions) {\n return null;\n }\n\n // Build layers array\n const layers: any[] = [];\n\n // 1. Filter active layers at this timestamp (uses clip-relative time for activeRanges)\n const activeLayers = instructions.layers.filter((layer: any) => {\n if (!layer.payload.attachmentId) {\n // Main track layer is always active\n return true;\n }\n if (layer.status !== 'ready') {\n return false;\n }\n // Check if layer is active at current timestamp\n return layer.activeRanges.some(\n (range: any) => clipRelativeTimeUs >= range.startUs && clipRelativeTimeUs < range.endUs\n );\n });\n\n // 2. Materialize layers\n for (const layerPlan of activeLayers) {\n const layer = await this.materializeLayer(layerPlan, clip, resourceTimeUs, timeUs);\n if (layer) {\n layers.push(layer);\n }\n }\n\n return { layers };\n }\n\n /**\n * Materialize a serialized layer plan into concrete Layer\n */\n private async materializeLayer(\n layerPlan: any,\n clip: Clip,\n resourceTimeUs: TimeUs,\n globalTimeUs: TimeUs\n ): Promise<any | null> {\n const baseLayer: any = {\n id: layerPlan.layerId,\n type: layerPlan.type,\n zIndex: layerPlan.zIndex ?? 0,\n visible: true,\n opacity: layerPlan.opacity ?? 1,\n };\n\n // Video layer - fetch raw VideoFrame from L1 (RcFrame wrapper)\n if (layerPlan.type === 'video' && !layerPlan.payload.attachmentId) {\n const rcFrame = this.cacheManager.getFrame(resourceTimeUs, clip.id);\n if (!rcFrame) {\n console.warn('[Orchestrator] Video frame not found in L1:', clip.id, resourceTimeUs);\n return null;\n }\n\n return {\n ...baseLayer,\n type: 'video',\n rcFrame: rcFrame,\n };\n }\n\n // Text layer\n if (layerPlan.type === 'text') {\n const payload = layerPlan.payload;\n return {\n ...baseLayer,\n type: 'text',\n text: payload.text,\n localeCode: payload.localeCode,\n fontConfig: payload.fontConfig,\n animation: payload.animation,\n wordTimings: payload.wordTimings,\n letterCase: payload.letterCase,\n };\n }\n\n // Image layer\n if (layerPlan.type === 'image') {\n const payload = layerPlan.payload;\n const resource = this.compositionModel?.getResource(payload.resourceId);\n if (!resource) {\n return null;\n }\n\n const source = await this.resourceLoader.loadImage(resource);\n const imageLayer: any = {\n ...baseLayer,\n type: 'image',\n source,\n attachmentId: payload.attachmentId,\n };\n\n if (payload.targetWidth !== undefined) {\n imageLayer.targetWidth = payload.targetWidth;\n }\n if (payload.targetHeight !== undefined) {\n imageLayer.targetHeight = payload.targetHeight;\n }\n\n // Handle animation (overlays)\n if (payload.animation) {\n const { position, keyframes, overlayClipStartUs } = payload.animation;\n\n // Calculate time relative to overlay clip start\n const relativeTimeUs = globalTimeUs - overlayClipStartUs;\n\n // If outside keyframe range, hide\n if (relativeTimeUs < 0 || relativeTimeUs > keyframes[keyframes.length - 1].time) {\n return null; // Not visible at this time\n }\n\n const rotationRad = 0; // TODO: interpolate from keyframes\n\n imageLayer.transform = {\n x: position.x,\n y: position.y,\n scaleX: 1,\n scaleY: 1,\n rotation: rotationRad,\n anchorX: 0.5,\n anchorY: 0.5,\n };\n }\n\n return imageLayer;\n }\n\n return baseLayer;\n }\n}\n"],"names":["applyModelPatch"],"mappings":"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"}
|
|
@@ -16,7 +16,6 @@ interface VideoClipSessionCallbacks {
|
|
|
16
16
|
clipDurationUs: number;
|
|
17
17
|
}): void;
|
|
18
18
|
onStreamDisposed?(): void;
|
|
19
|
-
onPipelineReady?(attachmentResourceIds?: string[]): Promise<void>;
|
|
20
19
|
}
|
|
21
20
|
interface VideoClipSessionConfig {
|
|
22
21
|
clipId: string;
|
|
@@ -62,6 +61,11 @@ export declare class VideoClipSession {
|
|
|
62
61
|
private getClip;
|
|
63
62
|
private getResource;
|
|
64
63
|
private extractAttachmentImageResources;
|
|
64
|
+
/**
|
|
65
|
+
* Load and transfer attachment images to ComposeWorker
|
|
66
|
+
* Must be called after workers are created and before sending video stream
|
|
67
|
+
*/
|
|
68
|
+
private loadAndTransferAttachments;
|
|
65
69
|
private ensureInstructions;
|
|
66
70
|
private setupImagePipeline;
|
|
67
71
|
private connectImagePipeline;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoClipSession.d.ts","sourceRoot":"","sources":["../../src/orchestrator/VideoClipSession.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,gBAAgB,EAAQ,MAAM,UAAU,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAGjF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAIpE,UAAU,yBAAyB;IACjC,oBAAoB,CAClB,MAAM,EAAE,cAAc,CAAC;QAAE,KAAK,EAAE,iBAAiB,CAAC;QAAC,QAAQ,EAAE,yBAAyB,CAAA;KAAE,CAAC,EACzF,KAAK,EAAE,OAAO,GAAG,OAAO,GACvB,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,kBAAkB,CAAC,CACjB,MAAM,EAAE,cAAc,CAAC,SAAS,CAAC,EACjC,QAAQ,EAAE;QACR,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,cAAc,EAAE,MAAM,CAAC;KACxB,GACA,IAAI,CAAC;IACR,gBAAgB,CAAC,IAAI,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"VideoClipSession.d.ts","sourceRoot":"","sources":["../../src/orchestrator/VideoClipSession.ts"],"names":[],"mappings":"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"}
|
|
@@ -34,13 +34,13 @@ class VideoClipSession {
|
|
|
34
34
|
}
|
|
35
35
|
async activate() {
|
|
36
36
|
if (this.isActive || this.isDisposed) return;
|
|
37
|
+
await this.ensureInstructions();
|
|
37
38
|
const clip = this.getClip();
|
|
38
39
|
const resource = this.getResource();
|
|
39
40
|
if (!clip || !resource) {
|
|
40
41
|
console.warn("[VideoClipSession] activate: clip or resource not found", this.sessionId);
|
|
41
42
|
return;
|
|
42
43
|
}
|
|
43
|
-
await this.ensureInstructions(clip);
|
|
44
44
|
if (resource.type === "video") {
|
|
45
45
|
await this.setupVideoPipeline(clip, resource);
|
|
46
46
|
} else if (resource.type === "image") {
|
|
@@ -54,10 +54,6 @@ class VideoClipSession {
|
|
|
54
54
|
);
|
|
55
55
|
}
|
|
56
56
|
this.isActive = true;
|
|
57
|
-
const attachmentResources = this.extractAttachmentImageResources();
|
|
58
|
-
if (this.callbacks.onPipelineReady && attachmentResources.length > 0) {
|
|
59
|
-
await this.callbacks.onPipelineReady(attachmentResources);
|
|
60
|
-
}
|
|
61
57
|
}
|
|
62
58
|
async deactivate() {
|
|
63
59
|
if (!this.isActive || this.isDisposed) return;
|
|
@@ -119,12 +115,42 @@ class VideoClipSession {
|
|
|
119
115
|
}
|
|
120
116
|
return Array.from(resourceIdSet);
|
|
121
117
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Load and transfer attachment images to ComposeWorker
|
|
120
|
+
* Must be called after workers are created and before sending video stream
|
|
121
|
+
*/
|
|
122
|
+
async loadAndTransferAttachments(sessionId, clipId) {
|
|
123
|
+
const attachmentResources = this.extractAttachmentImageResources();
|
|
124
|
+
if (attachmentResources.length === 0 || !this.resourceLoader) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
await Promise.all(
|
|
128
|
+
attachmentResources.map(async (resourceId) => {
|
|
129
|
+
const resource = this.compositionModel.getResource(resourceId);
|
|
130
|
+
if (!resource) {
|
|
131
|
+
console.warn(`[VideoClipSession] Resource not found: ${resourceId}`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const imageBitmap = await this.resourceLoader.loadImage(resource);
|
|
135
|
+
if (!imageBitmap) {
|
|
136
|
+
console.warn(`[VideoClipSession] Failed to load attachment: ${resourceId}`);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
await this.composeWorker.send(
|
|
140
|
+
"receive_image",
|
|
141
|
+
{ clipId, resourceId, sessionId, imageBitmap },
|
|
142
|
+
{ transfer: [imageBitmap] }
|
|
143
|
+
);
|
|
144
|
+
})
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
async ensureInstructions() {
|
|
148
|
+
const clip = this.getClip();
|
|
149
|
+
if (!clip) {
|
|
150
|
+
throw new Error(`Clip ${this.clipId} not found`);
|
|
126
151
|
}
|
|
127
|
-
|
|
152
|
+
const plan = this.planner.buildClipPlan(clip, { cache: false });
|
|
153
|
+
await this.installInstructions(plan.instructions);
|
|
128
154
|
}
|
|
129
155
|
async setupImagePipeline(clip) {
|
|
130
156
|
await this.acquireWorkers(clip);
|
|
@@ -172,6 +198,7 @@ class VideoClipSession {
|
|
|
172
198
|
metadata?.streamType ?? "video"
|
|
173
199
|
);
|
|
174
200
|
});
|
|
201
|
+
await this.loadAndTransferAttachments(sessionId, clip.id);
|
|
175
202
|
if (hasResourceId(clip)) {
|
|
176
203
|
const resource = this.getResource();
|
|
177
204
|
if (!resource) {
|
|
@@ -278,6 +305,7 @@ class VideoClipSession {
|
|
|
278
305
|
metadata?.streamType ?? "video"
|
|
279
306
|
);
|
|
280
307
|
});
|
|
308
|
+
await this.loadAndTransferAttachments(sessionId, clip.id);
|
|
281
309
|
const trimStartUs = clip.trimStartUs ?? 0;
|
|
282
310
|
const trimEndUs = clip.trimEndUs ?? trimStartUs + clip.durationUs;
|
|
283
311
|
const frameStream = await this.onDemandSession.decodeRangeToStream(trimStartUs, trimEndUs);
|