@@ -93,6 +93,16 @@ export function resolveSnapshotVideoFrameTime(input: {
9393 return Math . max ( 0 , Math . min ( relativeTime , sourceEnd - 1 / 30 ) ) ;
9494}
9595
96+ /** Convert a video's scene-local authored start into root timeline time when
97+ * it lives inside a template-mounted composition host. Top-level videos have
98+ * no template host and therefore retain their authored start unchanged. */
99+ export function resolveSnapshotVideoClipStart ( input : {
100+ authoredStart : number ;
101+ templateHostStart : number | null ;
102+ } ) : number {
103+ return input . authoredStart + ( input . templateHostStart ?? 0 ) ;
104+ }
105+
96106export function requireSnapshotFfmpeg ( ffmpegPath : string | undefined ) : string {
97107 if ( ffmpegPath ) return ffmpegPath ;
98108 throw new Error (
@@ -399,10 +409,16 @@ async function captureSnapshots(
399409 if ( cameraExpr ) await page . evaluate ( cameraExpr ) ;
400410
401411 if ( injectVideoFramesBatch && syncVideoFrameVisibility ) {
402- const candidates = await page . evaluate ( ( t : number ) => {
412+ const candidates = await page . evaluate ( ( ) => {
403413 return Array . from ( document . querySelectorAll ( "video[data-start]" ) ) . map ( ( el ) => {
404414 const v = el as HTMLVideoElement ;
405- const start = parseFloat ( v . dataset . start ?? "0" ) || 0 ;
415+ const authoredStart = parseFloat ( v . dataset . start ?? "0" ) || 0 ;
416+ const templateHost = v . closest < HTMLElement > (
417+ "[data-composition-src], [data-composition-file]" ,
418+ ) ;
419+ const templateHostStart = templateHost
420+ ? parseFloat ( templateHost . dataset . start ?? "0" ) || 0
421+ : null ;
406422 const rawRate = v . defaultPlaybackRate ;
407423 const playbackRate =
408424 Number . isFinite ( rawRate ) && rawRate > 0 ? Math . max ( 0.1 , Math . min ( 5 , rawRate ) ) : 1 ;
@@ -416,30 +432,40 @@ async function captureSnapshots(
416432 : srcDur > 0
417433 ? Math . max ( 0 , ( srcDur - mediaStart ) / playbackRate )
418434 : Number . POSITIVE_INFINITY ;
419- let relTime = ( t - start ) * playbackRate + mediaStart ;
420- if ( v . loop && srcDur > mediaStart && relTime >= srcDur ) {
421- relTime = mediaStart + ( ( relTime - mediaStart ) % ( srcDur - mediaStart ) ) ;
422- }
423435 return {
424436 id : v . id ,
425437 src : v . currentSrc || v . src ,
426- start,
438+ authoredStart,
439+ templateHostStart,
427440 duration,
428441 srcDuration : srcDur ,
429- relTime,
442+ playbackRate,
443+ mediaStart,
444+ loop : v . loop ,
430445 } ;
431446 } ) ;
432- } , time ) ;
447+ } ) ;
433448 const active = candidates . flatMap ( ( candidate ) => {
449+ const start = resolveSnapshotVideoClipStart ( candidate ) ;
450+ let relTime = ( time - start ) * candidate . playbackRate + candidate . mediaStart ;
451+ if (
452+ candidate . loop &&
453+ candidate . srcDuration > candidate . mediaStart &&
454+ relTime >= candidate . srcDuration
455+ ) {
456+ relTime =
457+ candidate . mediaStart +
458+ ( ( relTime - candidate . mediaStart ) % ( candidate . srcDuration - candidate . mediaStart ) ) ;
459+ }
434460 if ( ! candidate . id || ! candidate . src ) return [ ] ;
435461 const frameTime = resolveSnapshotVideoFrameTime ( {
436462 globalTime : time ,
437- clipStart : candidate . start ,
463+ clipStart : start ,
438464 clipDuration : candidate . duration ,
439- relativeTime : candidate . relTime ,
465+ relativeTime : relTime ,
440466 sourceDuration : candidate . srcDuration ,
441467 } ) ;
442- return frameTime === null ? [ ] : [ { ...candidate , relTime : frameTime } ] ;
468+ return frameTime === null ? [ ] : [ { ...candidate , start , relTime : frameTime } ] ;
443469 } ) ;
444470
445471 const updates : Array < { videoId : string ; dataUri : string } > = [ ] ;
0 commit comments