From 6db518e2a0ecfc1e65df52d1423b7bcd45524282 Mon Sep 17 00:00:00 2001 From: Abdulkabir Musa Date: Sun, 12 Jul 2026 07:06:35 +0100 Subject: [PATCH 1/2] fix: use static layer IDs to prevent sticker/text blinking - Changed layerId from dynamic clip.id-evalTime to static clip.id - Prevents recreation of Lottie players, text canvases, and WebGL textures on every frame - Resolves blinking issue and improves rendering performance - Audio layers now use consistent clip.id-audio format --- src/core/evaluation/evaluator.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/evaluation/evaluator.ts b/src/core/evaluation/evaluator.ts index ed6a0c48..476dccb0 100644 --- a/src/core/evaluation/evaluator.ts +++ b/src/core/evaluation/evaluator.ts @@ -161,7 +161,7 @@ export function evaluateTimelineScene(time: number, clips: Clip[], tracks: Track const finalHeight = evalH * animationState.scale; const textLayer: EvaluatedTextLayer = { - layerId: `${clip.id}-${evalTime}`, + layerId: clip.id, clipId: clip.id, role: clip.role, clipKind: clip.kind, @@ -232,7 +232,7 @@ export function evaluateTimelineScene(time: number, clips: Clip[], tracks: Track const transitionState = evaluateTransitionState(clip, transitionWindows); const mediaLayer: EvaluatedMediaLayer = { - layerId: `${clip.id}-${evalTime}`, + layerId: clip.id, clipId: clip.id, role: clip.role, clipKind: clip.kind, @@ -316,7 +316,7 @@ export function evaluateTimelineScene(time: number, clips: Clip[], tracks: Track if (!sourcePath) continue; audioLayers.push({ - layerId: `${clip.id}-audio-${evalTime}`, + layerId: `${clip.id}-audio`, clipId: clip.id, mediaId: clip.mediaId, sourcePath, From 003148ecc509134a8ef679f87c90ab1d81716a63 Mon Sep 17 00:00:00 2001 From: Abdulkabir Musa Date: Sun, 12 Jul 2026 07:06:43 +0100 Subject: [PATCH 2/2] fix: support audio library clips in export pipeline - Updated getActiveAudioClips to check for direct audioPath fallback property - Allows export of audio library items that aren't in project media assets - Mirrors preview player logic for consistent audio handling - Resolves issue where dedicated audio clips weren't exported --- src/core/timeline/audioClips.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/core/timeline/audioClips.ts b/src/core/timeline/audioClips.ts index 3b874e72..2f10f036 100644 --- a/src/core/timeline/audioClips.ts +++ b/src/core/timeline/audioClips.ts @@ -63,10 +63,10 @@ export function getActiveAudioClips(clips: Clip[], tracks: Track[], assets: Medi // Find asset const asset = assets.find((a) => a.id === clip.mediaId); - if (!asset) return false; + const directAudioPath = (clip as any).audioPath as string | undefined; + const isAudioClip = asset?.type === "audio" || asset?.type === "video" || (clip.kind === "audio" && !!directAudioPath); - // Only include audio and video clips (video may have audio track) - if (asset.type !== "audio" && asset.type !== "video") return false; + if (!isAudioClip) return false; // Check if clip overlaps with export time range const clipStart = clip.startTime; @@ -74,7 +74,9 @@ export function getActiveAudioClips(clips: Clip[], tracks: Track[], assets: Medi return clipStart < endTime && clipEnd > startTime; }) .map((clip) => { - const asset = assets.find((a) => a.id === clip.mediaId)!; + const asset = assets.find((a) => a.id === clip.mediaId); + const directAudioPath = (clip as any).audioPath as string | undefined; + const rawPath = asset ? asset.path : directAudioPath!; // Calculate overlap with export time range const clipStart = clip.startTime; @@ -97,8 +99,8 @@ export function getActiveAudioClips(clips: Clip[], tracks: Track[], assets: Medi const volume = Math.max(0, Math.min(1, clip.volume ?? 1.0)); return { - // Normalize to native FS path — asset.path may be an asset:// or file:// URL - path: toNativePath(asset.path), + // Normalize to native FS path — asset.path or directAudioPath may be an asset:// or file:// URL + path: toNativePath(rawPath), startTime: relativeStartTime, duration: relativeDuration, trimIn: relativeTrimIn,