@@ -4,7 +4,14 @@ import { PROJECT_SETTINGS } from "../../../project/project";
44import { useCurrentFrame } from "../frame" ;
55import { useClipActive , useClipStart , useProvideClipDuration } from "../clip" ;
66import { createManualPromise , type ManualPromise } from "../../util/promise" ;
7- import { normalizeVideo , video_fps , video_length , type Video , type VideoResolvedTrimProps } from "./video" ;
7+ import {
8+ normalizeVideo ,
9+ video_fps ,
10+ video_frame_count ,
11+ video_length ,
12+ type Video ,
13+ type VideoResolvedTrimProps ,
14+ } from "./video" ;
815
916// Track pending frame draws so headless callers can await completion.
1017const pendingFramePromises = new Set < Promise < void > > ( ) ;
@@ -60,6 +67,7 @@ export const VideoCanvasRender = ({ video, style, trimStartFrames = 0, trimEndFr
6067 const reconnectTimerRef = useRef < number | null > ( null ) ;
6168 const resolved = useMemo ( ( ) => normalizeVideo ( video ) , [ video ] ) ;
6269 const fps = useMemo ( ( ) => video_fps ( resolved ) , [ resolved ] ) ;
70+ const sourceFrameCount = useMemo ( ( ) => video_frame_count ( resolved ) , [ resolved ] ) ;
6371 const rawDurationFrames = useMemo ( ( ) => video_length ( resolved ) , [ resolved ] ) ;
6472 const durationFrames = Math . max ( 0 , rawDurationFrames - trimStartFrames - trimEndFrames ) ;
6573 useProvideClipDuration ( durationFrames ) ;
@@ -81,6 +89,10 @@ export const VideoCanvasRender = ({ video, style, trimStartFrames = 0, trimEndFr
8189
8290 const resize = ( ) => {
8391 const rect = canvas . getBoundingClientRect ( ) ;
92+ if ( rect . width <= 0 || rect . height <= 0 ) {
93+ // Hidden clips report 0x0; keep previous size to avoid requesting 1x1 frames.
94+ return ;
95+ }
8496 const dpr = window . devicePixelRatio || 1 ;
8597 const nextWidth = Math . max ( 1 , Math . round ( rect . width * dpr ) ) ;
8698 const nextHeight = Math . max ( 1 , Math . round ( rect . height * dpr ) ) ;
@@ -139,8 +151,8 @@ export const VideoCanvasRender = ({ video, style, trimStartFrames = 0, trimEndFr
139151
140152 const req = {
141153 video : resolved . path ,
142- width : canvasSizeRef . current . width ,
143- height : canvasSizeRef . current . height ,
154+ width : canvasSizeRef . current . width > 1 ? canvasSizeRef . current . width : PROJECT_SETTINGS . width ,
155+ height : canvasSizeRef . current . height > 1 ? canvasSizeRef . current . height : PROJECT_SETTINGS . height ,
144156 frame : playbackFrame ,
145157 } ;
146158
@@ -154,15 +166,27 @@ export const VideoCanvasRender = ({ video, style, trimStartFrames = 0, trimEndFr
154166 const clampedFrame =
155167 maxFrame !== undefined ? Math . min ( Math . max ( frame , 0 ) , maxFrame ) : Math . max ( frame , 0 ) ;
156168
157- const sourceStart = trimStartFrames ;
158- const sourceEnd = Math . max ( sourceStart , rawDurationFrames - trimEndFrames - 1 ) ;
169+ const sourceStart =
170+ fps > 0
171+ ? Math . floor ( ( trimStartFrames * fps ) / PROJECT_SETTINGS . fps )
172+ : trimStartFrames ;
173+ const sourceTrimEnd =
174+ fps > 0
175+ ? Math . floor ( ( trimEndFrames * fps ) / PROJECT_SETTINGS . fps )
176+ : trimEndFrames ;
177+ const estimatedSourceFrames =
178+ fps > 0
179+ ? Math . max ( 0 , Math . round ( ( rawDurationFrames * fps ) / PROJECT_SETTINGS . fps ) )
180+ : rawDurationFrames ;
181+ const sourceTotalFrames = sourceFrameCount > 0 ? sourceFrameCount : estimatedSourceFrames ;
182+ const sourceEnd = Math . max ( sourceStart , sourceTotalFrames - sourceTrimEnd - 1 ) ;
159183
160184 requestedFrameRef . current = clampedFrame ;
161185
162186 const playbackFrameRaw =
163187 fps > 0
164- ? Math . round ( ( ( clampedFrame + sourceStart ) * fps ) / PROJECT_SETTINGS . fps )
165- : clampedFrame + sourceStart ;
188+ ? Math . floor ( ( ( clampedFrame + trimStartFrames ) * fps ) / PROJECT_SETTINGS . fps )
189+ : clampedFrame + trimStartFrames ;
166190 const playbackFrame = Math . min ( Math . max ( playbackFrameRaw , sourceStart ) , sourceEnd ) ;
167191
168192 const alreadyDrawn =
@@ -182,7 +206,15 @@ export const VideoCanvasRender = ({ video, style, trimStartFrames = 0, trimEndFr
182206
183207 sendPlaybackFrameRequest ( playbackFrame ) ;
184208 } ,
185- [ durationFrames , fps , rawDurationFrames , sendPlaybackFrameRequest , trimEndFrames , trimStartFrames ] ,
209+ [
210+ durationFrames ,
211+ fps ,
212+ rawDurationFrames ,
213+ sendPlaybackFrameRequest ,
214+ sourceFrameCount ,
215+ trimEndFrames ,
216+ trimStartFrames ,
217+ ] ,
186218 ) ;
187219
188220 useEffect ( ( ) => {
@@ -271,10 +303,8 @@ export const VideoCanvasRender = ({ video, style, trimStartFrames = 0, trimEndFr
271303 pending ?. projectFrame ??
272304 Math . max (
273305 0 ,
274- Math . round (
275- ( ( frameIndex - trimStartFrames ) * PROJECT_SETTINGS . fps ) /
276- Math . max ( 1 , fps || PROJECT_SETTINGS . fps ) ,
277- ) ,
306+ Math . round ( ( frameIndex * PROJECT_SETTINGS . fps ) / Math . max ( 1 , fps || PROJECT_SETTINGS . fps ) ) -
307+ trimStartFrames ,
278308 ) ;
279309
280310 if ( pending ) {
@@ -310,6 +340,13 @@ export const VideoCanvasRender = ({ video, style, trimStartFrames = 0, trimEndFr
310340 } ;
311341 } , [ rejectPendingRequests , resolveWaiters , sendFrameRequest , sendPlaybackFrameRequest , visible ] ) ;
312342
343+ useEffect ( ( ) => {
344+ if ( visible ) return ;
345+ // Re-entering a clip must not reuse stale last-drawn markers.
346+ lastDrawnFrameRef . current = null ;
347+ requestedFrameRef . current = null ;
348+ } , [ visible ] ) ;
349+
313350 useEffect ( ( ) => {
314351 if ( ! visible ) return ;
315352 sendFrameRequest ( currentFrame ) ;
@@ -323,11 +360,15 @@ export const VideoCanvasRender = ({ video, style, trimStartFrames = 0, trimEndFr
323360 }
324361 const startOffset = clipStart ?? 0
325362 const relativeFrame = frame - startOffset
326- if ( relativeFrame < 0 || durationFrames <= 0 ) return
363+ if ( relativeFrame < 0 || durationFrames <= 0 ) {
364+ return
365+ }
327366 const maxFrame = Math . max ( 0 , durationFrames - 1 )
328367 const clampedFrame = Math . min ( Math . max ( relativeFrame , 0 ) , maxFrame )
329368 const lastDrawn = lastDrawnFrameRef . current
330- if ( lastDrawn != null && lastDrawn >= clampedFrame ) return
369+ if ( lastDrawn != null && lastDrawn >= clampedFrame ) {
370+ return
371+ }
331372 await createOrGetFramePromise ( clampedFrame ) . promise
332373 }
333374
0 commit comments