From aada62b243fe3a3aa1e1287b47f7043070bfbc29 Mon Sep 17 00:00:00 2001 From: Sameer2748 Date: Thu, 19 Mar 2026 13:57:13 +0530 Subject: [PATCH] fix: restore editor preview responsiveness after export (#68) The Pixi.js canvas and video element in the editor preview panel could become "stalled" or "stale" after a heavy video export render. Because this is a long async process, the browser's Rendering/Pixi context sometimes fails to resume frame updates, leaving the preview frozen or unresponsive to interactions. Changes: - Added 'previewVersion' state to VideoEditor.tsx to drive component re-mounting. - Updated VideoEditor.tsx to increment 'previewVersion' in the finally block of the export flow (success, error, or cancel). - Applied 'previewVersion' to the key of the VideoPlayback component, forcing React to fully re-construct the preview context after each export. - Enhanced VideoPlayback.tsx to sync the internal time ref with the currentTime prop, ensuring the new player state is correctly seeked after re-mount. - Added a relayout() method to flush Pixi textures and force an immediate layout refresh for better responsiveness. Fixes #68 --- src/components/video-editor/VideoEditor.tsx | 9 +++--- src/components/video-editor/VideoPlayback.tsx | 31 +++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index 214b78d..d132eea 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -157,6 +157,7 @@ export default function VideoEditor() { const [audioRegions, setAudioRegions] = useState([]); const [selectedAudioId, setSelectedAudioId] = useState(null); const [isExporting, setIsExporting] = useState(false); + const [previewVersion, setPreviewVersion] = useState(0); const [exportProgress, setExportProgress] = useState(null); const [exportError, setExportError] = useState(null); const [showExportDialog, setShowExportDialog] = useState(false); @@ -1774,13 +1775,11 @@ export default function VideoEditor() { setExportError(errorMessage); toast.error(`Export failed: ${errorMessage}`); } finally { - if (!isPlaying) { - await videoPlaybackRef.current?.refreshFrame().catch(() => undefined); - } setIsExporting(false); exporterRef.current = null; setShowExportDialog(keepExportDialogOpen); setExportProgress(null); + setPreviewVersion(v => v + 1); } }, [ @@ -1881,6 +1880,7 @@ export default function VideoEditor() { setExportProgress(null); setExportError(null); setExportedFilePath(undefined); + setPreviewVersion(v => v + 1); } }, []); @@ -1997,6 +1997,7 @@ export default function VideoEditor() { >
Promise; pause: () => void; refreshFrame: () => Promise; + relayout: () => void; } const VideoPlayback = forwardRef( @@ -462,6 +463,31 @@ const VideoPlayback = forwardRef( video.currentTime = nudgeTarget; }); }, + relayout: () => { + const fn = layoutVideoContentRef.current; + if (fn) { + if (videoSpriteRef.current?.texture?.source) { + try { + videoSpriteRef.current.texture.source.update(); + } catch (e) { + console.warn("[VideoPlayback] Failed to update texture source during relayout:", e); + } + } + + requestAnimationFrame(() => { + fn(); + if (cursorOverlayRef.current) { + cursorOverlayRef.current.update( + cursorTelemetryRef.current, + currentTimeRef.current, + baseMaskRef.current, + showCursorRef.current, + !isPlayingRef.current || isSeekingRef.current, + ); + } + }); + } + }, })); const updateFocusFromClientPoint = (clientX: number, clientY: number) => { @@ -597,6 +623,11 @@ const VideoPlayback = forwardRef( cursorSwayRef.current = cursorSway; }, [cursorSway]); + // Sync currentTime prop to internal ref (in milliseconds) + useEffect(() => { + currentTimeRef.current = currentTime * 1000; + }, [currentTime]); + useEffect(() => { if (!pixiReady || !videoReady) return;