fix(video-playback): resolve initialization timing issues and ensure smooth zoom & layout rendering

This commit is contained in:
AmitwalaH
2026-04-23 15:10:59 +05:30
parent 24928164ca
commit 9a361a9f2e
2 changed files with 64 additions and 23 deletions
+60 -19
View File
@@ -164,6 +164,10 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
const [pixiReady, setPixiReady] = useState(false);
const [videoReady, setVideoReady] = useState(false);
const overlayRef = useRef<HTMLDivElement | null>(null);
const [containerSize, setContainerSize] = useState({
width: 800,
height: 600,
});
const focusIndicatorRef = useRef<HTMLDivElement | null>(null);
const [webcamLayout, setWebcamLayout] = useState<StyledRenderRect | null>(null);
const [webcamDimensions, setWebcamDimensions] = useState<Size | null>(null);
@@ -195,7 +199,10 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
const isPlayingRef = useRef(isPlaying);
const isSeekingRef = useRef(false);
const allowPlaybackRef = useRef(false);
const lockedVideoDimensionsRef = useRef<{ width: number; height: number } | null>(null);
const lockedVideoDimensionsRef = useRef<{
width: number;
height: number;
} | null>(null);
const layoutVideoContentRef = useRef<(() => void) | null>(null);
const trimRegionsRef = useRef<TrimRegion[]>([]);
const speedRegionsRef = useRef<SpeedRegion[]>([]);
@@ -648,7 +655,11 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
app.ticker.maxFPS = 60;
if (!mounted) {
app.destroy(true, { children: true, texture: true, textureSource: true });
app.destroy(true, {
children: true,
texture: true,
textureSource: true,
});
return;
}
@@ -672,7 +683,11 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
mounted = false;
setPixiReady(false);
if (app && app.renderer) {
app.destroy(true, { children: true, texture: true, textureSource: true });
app.destroy(true, {
children: true,
texture: true,
textureSource: true,
});
}
appRef.current = null;
cameraContainerRef.current = null;
@@ -853,12 +868,19 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
const ss = stageSizeRef.current;
const viewportRatio =
bm.width > 0 && bm.height > 0
? { widthRatio: ss.width / bm.width, heightRatio: ss.height / bm.height }
? {
widthRatio: ss.width / bm.width,
heightRatio: ss.height / bm.height,
}
: undefined;
const { region, strength, blendedScale, transition } = findDominantRegion(
zoomRegionsRef.current,
currentTimeRef.current,
{ connectZooms: true, cursorTelemetry: cursorTelemetryRef.current, viewportRatio },
{
connectZooms: true,
cursorTelemetry: cursorTelemetryRef.current,
viewportRatio,
},
);
const defaultFocus = DEFAULT_FOCUS;
@@ -1113,6 +1135,21 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
webcamVideo.currentTime = 0;
}, [webcamVideoPath]);
useEffect(() => {
if (!overlayRef.current) return;
const observer = new ResizeObserver(() => {
setContainerSize({
width: overlayRef.current!.clientWidth,
height: overlayRef.current!.clientHeight,
});
});
observer.observe(overlayRef.current);
return () => observer.disconnect();
}, []);
useEffect(() => {
let mounted = true;
(async () => {
@@ -1307,20 +1344,24 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
}
};
return sorted.map((annotation) => (
<AnnotationOverlay
key={annotation.id}
annotation={annotation}
isSelected={annotation.id === selectedAnnotationId}
containerWidth={overlayRef.current?.clientWidth || 800}
containerHeight={overlayRef.current?.clientHeight || 600}
onPositionChange={(id, position) => onAnnotationPositionChange?.(id, position)}
onSizeChange={(id, size) => onAnnotationSizeChange?.(id, size)}
onClick={handleAnnotationClick}
zIndex={annotation.zIndex}
isSelectedBoost={annotation.id === selectedAnnotationId}
/>
));
return sorted.map((annotation) => {
const containerWidth = containerSize.width;
const containerHeight = containerSize.height;
return (
<AnnotationOverlay
key={annotation.id}
annotation={annotation}
isSelected={annotation.id === selectedAnnotationId}
containerWidth={containerWidth}
containerHeight={containerHeight}
onPositionChange={(id, position) => onAnnotationPositionChange?.(id, position)}
onSizeChange={(id, size) => onAnnotationSizeChange?.(id, size)}
onClick={handleAnnotationClick}
zIndex={annotation.zIndex}
isSelectedBoost={annotation.id === selectedAnnotationId}
/>
);
});
})()}
</div>
)}
+4 -4
View File
@@ -406,8 +406,8 @@ export class FrameRenderer {
this.compositeCtx
) {
// Calculate scale factor based on export vs preview dimensions
const previewWidth = this.config.previewWidth || 1920;
const previewHeight = this.config.previewHeight || 1080;
const previewWidth = this.config.previewWidth ?? this.config.width;
const previewHeight = this.config.previewHeight ?? this.config.height;
const scaleX = this.config.width / previewWidth;
const scaleY = this.config.height / previewHeight;
const scaleFactor = (scaleX + scaleY) / 2;
@@ -491,8 +491,8 @@ export class FrameRenderer {
this.videoContainer.y = screenRect.y;
// scale border radius by export/preview canvas ratio
const previewWidth = this.config.previewWidth || 1920;
const previewHeight = this.config.previewHeight || 1080;
const previewWidth = this.config.previewWidth ?? this.config.width;
const previewHeight = this.config.previewHeight ?? this.config.height;
const canvasScaleFactor = Math.min(width / previewWidth, height / previewHeight);
const scaledBorderRadius = compositeLayout.screenCover ? 0 : borderRadius * canvasScaleFactor;