From e3b38c00f10be8ba1b9b1d37080ea7392e974dd4 Mon Sep 17 00:00:00 2001 From: Siddharth Date: Sun, 9 Nov 2025 12:07:36 -0700 Subject: [PATCH] fix playback on load --- src/components/video-editor/VideoEditor.tsx | 11 ++--- src/components/video-editor/VideoPlayback.tsx | 34 +++++++++++++++ .../video-editor/videoPlayback/layoutUtils.ts | 7 +++- .../videoPlayback/videoEventHandlers.ts | 10 +++++ .../videoPlayback/zoomTransform.ts | 41 ++++++++++++++----- 5 files changed, 87 insertions(+), 16 deletions(-) diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index d85bff1..ade1a1c 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -57,14 +57,15 @@ export default function VideoEditor() { }, []); function togglePlayPause() { - const video = videoPlaybackRef.current?.video; + const playback = videoPlaybackRef.current; + const video = playback?.video; console.log('🎮 Toggle play/pause:', { hasVideo: !!video, isPlaying, action: isPlaying ? 'pause' : 'play' }); - if (!video) return; - + if (!playback || !video) return; + if (isPlaying) { - video.pause(); + playback.pause(); } else { - video.play().catch(err => console.error('❌ Video play failed:', err)); + playback.play().catch(err => console.error('❌ Video play failed:', err)); } } diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx index 83252c5..190ace1 100644 --- a/src/components/video-editor/VideoPlayback.tsx +++ b/src/components/video-editor/VideoPlayback.tsx @@ -33,6 +33,8 @@ export interface VideoPlaybackRef { app: PIXI.Application | null; videoSprite: PIXI.Sprite | null; videoContainer: PIXI.Container | null; + play: () => Promise; + pause: () => void; } const VideoPlayback = forwardRef(({ @@ -75,6 +77,7 @@ const VideoPlayback = forwardRef(({ const maskGraphicsRef = useRef(null); const isPlayingRef = useRef(isPlaying); const isSeekingRef = useRef(false); + const allowPlaybackRef = useRef(false); const clampFocusToStage = useCallback((focus: ZoomFocus, depth: ZoomDepth) => { return clampFocusToStageUtil(focus, depth, stageSizeRef.current); @@ -152,6 +155,28 @@ const VideoPlayback = forwardRef(({ app: appRef.current, videoSprite: videoSpriteRef.current, videoContainer: videoContainerRef.current, + play: async () => { + const video = videoRef.current; + if (!video) { + allowPlaybackRef.current = false; + return; + } + allowPlaybackRef.current = true; + try { + await video.play(); + } catch (error) { + allowPlaybackRef.current = false; + throw error; + } + }, + pause: () => { + const video = videoRef.current; + allowPlaybackRef.current = false; + if (!video) { + return; + } + video.pause(); + }, })); const updateFocusFromClientPoint = (clientX: number, clientY: number) => { @@ -330,6 +355,7 @@ const VideoPlayback = forwardRef(({ if (!video) return; video.pause(); video.currentTime = 0; + allowPlaybackRef.current = false; }, [videoPath]); useEffect(() => { @@ -343,6 +369,12 @@ const VideoPlayback = forwardRef(({ if (video.videoWidth === 0 || video.videoHeight === 0) return; const source = PIXI.VideoSource.from(video); + if ('autoPlay' in source) { + (source as { autoPlay?: boolean }).autoPlay = false; + } + if ('autoUpdate' in source) { + (source as { autoUpdate?: boolean }).autoUpdate = true; + } const videoTexture = PIXI.Texture.from(source); const videoSprite = new PIXI.Sprite(videoTexture); @@ -374,6 +406,7 @@ const VideoPlayback = forwardRef(({ video, isSeekingRef, isPlayingRef, + allowPlaybackRef, currentTimeRef, timeUpdateAnimationRef, onPlayStateChange, @@ -524,6 +557,7 @@ const VideoPlayback = forwardRef(({ onDurationChange(video.duration); video.currentTime = 0; video.pause(); + allowPlaybackRef.current = false; currentTimeRef.current = 0; setVideoReady(true); }; diff --git a/src/components/video-editor/videoPlayback/layoutUtils.ts b/src/components/video-editor/videoPlayback/layoutUtils.ts index a7c2582..997e550 100644 --- a/src/components/video-editor/videoPlayback/layoutUtils.ts +++ b/src/components/video-editor/videoPlayback/layoutUtils.ts @@ -17,6 +17,7 @@ interface LayoutResult { baseScale: number; baseOffset: { x: number; y: number }; maskRect: { x: number; y: number; width: number; height: number }; + cropOffset: { x: number; y: number }; } export function layoutVideoContent(params: LayoutParams): LayoutResult | null { @@ -90,10 +91,14 @@ export function layoutVideoContent(params: LayoutParams): LayoutResult | null { maskGraphics.fill({ color: 0xffffff }); return { - stageSize: { width, height }, + stageSize: { width: croppedDisplayWidth, height: croppedDisplayHeight }, videoSize: { width: croppedVideoWidth, height: croppedVideoHeight }, baseScale: scale, baseOffset: { x: spriteX, y: spriteY }, maskRect: { x: maskX, y: maskY, width: croppedDisplayWidth, height: croppedDisplayHeight }, + cropOffset: { + x: crop.x * videoWidth, + y: crop.y * videoHeight, + }, }; } diff --git a/src/components/video-editor/videoPlayback/videoEventHandlers.ts b/src/components/video-editor/videoPlayback/videoEventHandlers.ts index 06e6dec..515c9c0 100644 --- a/src/components/video-editor/videoPlayback/videoEventHandlers.ts +++ b/src/components/video-editor/videoPlayback/videoEventHandlers.ts @@ -2,6 +2,7 @@ interface VideoEventHandlersParams { video: HTMLVideoElement; isSeekingRef: React.MutableRefObject; isPlayingRef: React.MutableRefObject; + allowPlaybackRef: React.MutableRefObject; currentTimeRef: React.MutableRefObject; timeUpdateAnimationRef: React.MutableRefObject; onPlayStateChange: (playing: boolean) => void; @@ -13,6 +14,7 @@ export function createVideoEventHandlers(params: VideoEventHandlersParams) { video, isSeekingRef, isPlayingRef, + allowPlaybackRef, currentTimeRef, timeUpdateAnimationRef, onPlayStateChange, @@ -38,12 +40,20 @@ export function createVideoEventHandlers(params: VideoEventHandlersParams) { video.pause(); return; } + + if (!allowPlaybackRef.current) { + video.pause(); + return; + } + + allowPlaybackRef.current = false; isPlayingRef.current = true; onPlayStateChange(true); updateTime(); }; const handlePause = () => { + allowPlaybackRef.current = false; isPlayingRef.current = false; if (timeUpdateAnimationRef.current) { cancelAnimationFrame(timeUpdateAnimationRef.current); diff --git a/src/components/video-editor/videoPlayback/zoomTransform.ts b/src/components/video-editor/videoPlayback/zoomTransform.ts index 0147bd0..a40fb4b 100644 --- a/src/components/video-editor/videoPlayback/zoomTransform.ts +++ b/src/components/video-editor/videoPlayback/zoomTransform.ts @@ -9,6 +9,7 @@ interface TransformParams { baseScale: number; baseOffset: { x: number; y: number }; baseMask: { x: number; y: number; width: number; height: number }; + cropOffset: { x: number; y: number }; zoomScale: number; focusX: number; focusY: number; @@ -26,6 +27,7 @@ export function applyZoomTransform(params: TransformParams) { baseScale, baseOffset, baseMask, + cropOffset, zoomScale, focusX, focusY, @@ -45,10 +47,10 @@ export function applyZoomTransform(params: TransformParams) { return; } - const focusStagePxX = focusX * stageSize.width; - const focusStagePxY = focusY * stageSize.height; - const stageCenterX = stageSize.width / 2; - const stageCenterY = stageSize.height / 2; + const focusStagePxX = baseMask.x + focusX * stageSize.width; + const focusStagePxY = baseMask.y + focusY * stageSize.height; + const stageCenterX = baseMask.x + stageSize.width / 2; + const stageCenterY = baseMask.y + stageSize.height / 2; const actualScale = baseScale * zoomScale; videoSprite.scale.set(actualScale); @@ -62,8 +64,28 @@ export function applyZoomTransform(params: TransformParams) { const focusInVideoSpaceY = focusStagePxY - baseVideoY; // Position formula: stageCenterX - focusInVideoSpace * zoomScale - const newVideoX = stageCenterX - focusInVideoSpaceX * zoomScale; - const newVideoY = stageCenterY - focusInVideoSpaceY * zoomScale; + let newVideoX = stageCenterX - focusInVideoSpaceX * zoomScale; + let newVideoY = stageCenterY - focusInVideoSpaceY * zoomScale; + + const cropStartX = cropOffset.x; + const cropStartY = cropOffset.y; + const cropEndX = cropOffset.x + videoSize.width; + const cropEndY = cropOffset.y + videoSize.height; + const maskWidth = baseMask.width; + const maskHeight = baseMask.height; + + const minVideoX = baseMaskX + maskWidth - cropEndX * actualScale; + const maxVideoX = baseMaskX - cropStartX * actualScale; + const minVideoY = baseMaskY + maskHeight - cropEndY * actualScale; + const maxVideoY = baseMaskY - cropStartY * actualScale; + + if (minVideoX <= maxVideoX) { + newVideoX = Math.max(minVideoX, Math.min(maxVideoX, newVideoX)); + } + + if (minVideoY <= maxVideoY) { + newVideoY = Math.max(minVideoY, Math.min(maxVideoY, newVideoY)); + } videoSprite.position.set(newVideoX, newVideoY); @@ -73,11 +95,10 @@ export function applyZoomTransform(params: TransformParams) { blurFilter.blur = motionBlur; } - const maskWidth = baseMask.width * zoomScale; - const maskHeight = baseMask.height * zoomScale; - const maskX = baseMaskX + (newVideoX - baseVideoX); - const maskY = baseMaskY + (newVideoY - baseVideoY); + const maskX = baseMaskX; + const maskY = baseMaskY; const radius = Math.min(maskWidth, maskHeight) * 0.02; + maskGraphics.clear(); maskGraphics.roundRect(maskX, maskY, maskWidth, maskHeight, radius); maskGraphics.fill({ color: 0xffffff });