@@ -109,6 +109,18 @@ export function resolveSnapshotVideoClipStart(input: {
109109 return input . runtimeResolvedStart ?? input . authoredStart ;
110110}
111111
112+ /** Match runtime/render timing: authored data-playback-rate wins over the
113+ * browser default, then the effective rate is clamped to the supported range. */
114+ export function resolveSnapshotVideoPlaybackRate ( input : {
115+ authoredRate : string | undefined ;
116+ defaultRate : number ;
117+ } ) : number {
118+ const authoredRate = Number . parseFloat ( input . authoredRate ?? "" ) ;
119+ const rawRate =
120+ Number . isFinite ( authoredRate ) && authoredRate > 0 ? authoredRate : input . defaultRate ;
121+ return Number . isFinite ( rawRate ) && rawRate > 0 ? Math . max ( 0.1 , Math . min ( 5 , rawRate ) ) : 1 ;
122+ }
123+
112124export function requireSnapshotFfmpeg ( ffmpegPath : string | undefined ) : string {
113125 if ( ffmpegPath ) return ffmpegPath ;
114126 throw new Error (
@@ -425,38 +437,37 @@ async function captureSnapshots(
425437 const v = el as HTMLVideoElement ;
426438 const authoredStart = parseFloat ( v . dataset . start ?? "0" ) || 0 ;
427439 const runtimeResolvedStart = runtimeWindow . __hfResolveMediaStartSeconds ?.( v ) ;
428- const rawRate = v . defaultPlaybackRate ;
429- const playbackRate =
430- Number . isFinite ( rawRate ) && rawRate > 0 ? Math . max ( 0.1 , Math . min ( 5 , rawRate ) ) : 1 ;
431440 const mediaStart =
432441 parseFloat ( v . dataset . playbackStart ?? v . dataset . mediaStart ?? "0" ) || 0 ;
433442 const rawDuration = parseFloat ( v . dataset . duration ?? "" ) ;
434443 const srcDur = Number . isFinite ( v . duration ) && v . duration > 0 ? v . duration : 0 ;
435- const duration =
436- Number . isFinite ( rawDuration ) && rawDuration > 0
437- ? rawDuration
438- : srcDur > 0
439- ? Math . max ( 0 , ( srcDur - mediaStart ) / playbackRate )
440- : Number . POSITIVE_INFINITY ;
441444 return {
442445 id : v . id ,
443446 src : v . currentSrc || v . src ,
444447 authoredStart,
448+ authoredRate : v . dataset . playbackRate ,
449+ defaultRate : v . defaultPlaybackRate ,
445450 runtimeResolvedStart :
446451 runtimeResolvedStart !== undefined && Number . isFinite ( runtimeResolvedStart )
447452 ? runtimeResolvedStart
448453 : null ,
449- duration,
454+ authoredDuration :
455+ Number . isFinite ( rawDuration ) && rawDuration > 0 ? rawDuration : null ,
450456 srcDuration : srcDur ,
451- playbackRate,
452457 mediaStart,
453458 loop : v . loop ,
454459 } ;
455460 } ) ;
456461 } ) ;
457462 const active = candidates . flatMap ( ( candidate ) => {
458463 const start = resolveSnapshotVideoClipStart ( candidate ) ;
459- let relTime = ( time - start ) * candidate . playbackRate + candidate . mediaStart ;
464+ const playbackRate = resolveSnapshotVideoPlaybackRate ( candidate ) ;
465+ const duration =
466+ candidate . authoredDuration ??
467+ ( candidate . srcDuration > 0
468+ ? Math . max ( 0 , ( candidate . srcDuration - candidate . mediaStart ) / playbackRate )
469+ : Number . POSITIVE_INFINITY ) ;
470+ let relTime = ( time - start ) * playbackRate + candidate . mediaStart ;
460471 if (
461472 candidate . loop &&
462473 candidate . srcDuration > candidate . mediaStart &&
@@ -470,11 +481,13 @@ async function captureSnapshots(
470481 const frameTime = resolveSnapshotVideoFrameTime ( {
471482 globalTime : time ,
472483 clipStart : start ,
473- clipDuration : candidate . duration ,
484+ clipDuration : duration ,
474485 relativeTime : relTime ,
475486 sourceDuration : candidate . srcDuration ,
476487 } ) ;
477- return frameTime === null ? [ ] : [ { ...candidate , start, relTime : frameTime } ] ;
488+ return frameTime === null
489+ ? [ ]
490+ : [ { ...candidate , start, playbackRate, duration, relTime : frameTime } ] ;
478491 } ) ;
479492
480493 const updates : Array < { videoId : string ; dataUri : string } > = [ ] ;
0 commit comments